<?php

/**
 * Преобразует первый символ строки в кодировке UTF-8 в верхний регистр
 *
 * @param   string    $s
 * @parm    bool      $is_other_to_lowercase  остальные символы преобразуются в нижний регистр?
 * @return  string
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  2.0.1
 */
function utf8_ucfirst($s, $is_other_to_lowercase = true)
{
    if (preg_match('/^([a-zA-Z]  #английский (all)
                       | [a-zA-Z]|\xc3[\xa4\xa7\xb1\xb6\xbc\x84\x87\x91\x96\x9c]|\xc4[\x9f\xb1\x9e\xb0]|\xc5[\x9f\x9e]  #турецкие буквы (татарский латиница) (all)
                       | \xd0[\x90-\xbf\x81]|\xd1[\x80-\x8f\x91]  #русский А-я (all)
                       | \xd2[\x96\x97\xa2\xa3\xae\xaf\xba\xbb]|\xd3[\x98\x99\xa8\xa9]  #татарские буквы (all)
                      )     #1
                      (.*)  #2
                     $/sx', $s, $m))
    {
        if (! function_exists('utf8_convert_case'))  #оптимизация скорости include_once
        {
            include_once 'utf8_convert_case.php';
        }
        return utf8_convert_case($m[1], CASE_UPPER) . ($is_other_to_lowercase ? utf8_convert_case($m[2], CASE_LOWER) : $m[2]);
    }
    return $s;
}

/**
 * Конвертирует регистр букв в строке в кодировке UTF-8
 *
 * @param    string      $s     строка
 * @param    int         $mode  {CASE_LOWER|CASE_UPPER}
 * @return   string
 * @link     http://www.unicode.org/charts/PDF/U0400.pdf
 * @link     http://ru.wikipedia.org/wiki/ISO_639-1
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  1.2.3
 */
function utf8_convert_case($s, $mode)
{

/**
 * UTF-8 Case lookup table
 *
 * This lookuptable defines the upper case letters to their correspponding
 * lower case letter in UTF-8
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */
/***
$UTF8_LOWER_TO_UPPER = array(
  0x0061=>0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042,
  0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100,
  0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393,
  0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C,
  0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F,
  0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E,
  0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3,
  0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A,
  0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9,
  0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C,
  0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4,
  0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164,
  0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156,
  0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118,
  0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128,
  0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428,
  0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055,
  0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A,
  0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC,
  0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0,
  0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D,
  0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0,
  0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5,
  0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA,
  0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045,
  0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F,
  0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048,
  0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6,
  0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407,
  0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395,
  0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396,
  0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051,
  0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408,
  0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F,
  0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126,
  0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C,
  0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E,
  0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB,
  0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421,
  0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A,
  0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102,
  0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9,
  0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122,
);
***/

/**
 * UTF-8 array of common special characters
 *
 * This array should contain all special characters (not a letter or digit)
 * defined in the various local charsets - it's not a complete list of non-alphanum
 * characters in UTF-8. It's not perfect but should match most cases of special
 * chars.
 *
 * The controlchars 0x00 to 0x19 are _not_ included in this array. The space 0x20 is!
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 * @see    utf8_stripspecials()
 */
/***
$UTF8_SPECIAL_CHARS = array(
  0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023,
  0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d,
  0x002e, 0x002f, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x005b,
  0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e,
  0x007f, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088,
  0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 0x0090, 0x0091, 0x0092,
        0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c,
        0x009d, 0x009e, 0x009f, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6,
        0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0,
        0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba,
        0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, 0x00d7, 0x00f7, 0x02c7, 0x02d8, 0x02d9,
        0x02da, 0x02db, 0x02dc, 0x02dd, 0x0300, 0x0301, 0x0303, 0x0309, 0x0323, 0x0384,
        0x0385, 0x0387, 0x03b2, 0x03c6, 0x03d1, 0x03d2, 0x03d5, 0x03d6, 0x05b0, 0x05b1,
        0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, 0x05b8, 0x05b9, 0x05bb, 0x05bc,
        0x05bd, 0x05be, 0x05bf, 0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05f3, 0x05f4, 0x060c,
        0x061b, 0x061f, 0x0640, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f, 0x0650, 0x0651,
        0x0652, 0x066a, 0x0e3f, 0x200c, 0x200d, 0x200e, 0x200f, 0x2013, 0x2014, 0x2015,
        0x2017, 0x2018, 0x2019, 0x201a, 0x201c, 0x201d, 0x201e, 0x2020, 0x2021, 0x2022,
        0x2026, 0x2030, 0x2032, 0x2033, 0x2039, 0x203a, 0x2044, 0x20a7, 0x20aa, 0x20ab,
        0x20ac, 0x2116, 0x2118, 0x2122, 0x2126, 0x2135, 0x2190, 0x2191, 0x2192, 0x2193,
        0x2194, 0x2195, 0x21b5, 0x21d0, 0x21d1, 0x21d2, 0x21d3, 0x21d4, 0x2200, 0x2202,
        0x2203, 0x2205, 0x2206, 0x2207, 0x2208, 0x2209, 0x220b, 0x220f, 0x2211, 0x2212,
        0x2215, 0x2217, 0x2219, 0x221a, 0x221d, 0x221e, 0x2220, 0x2227, 0x2228, 0x2229,
        0x222a, 0x222b, 0x2234, 0x223c, 0x2245, 0x2248, 0x2260, 0x2261, 0x2264, 0x2265,
        0x2282, 0x2283, 0x2284, 0x2286, 0x2287, 0x2295, 0x2297, 0x22a5, 0x22c5, 0x2310,
        0x2320, 0x2321, 0x2329, 0x232a, 0x2469, 0x2500, 0x2502, 0x250c, 0x2510, 0x2514,
        0x2518, 0x251c, 0x2524, 0x252c, 0x2534, 0x253c, 0x2550, 0x2551, 0x2552, 0x2553,
        0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d,
        0x255e, 0x255f, 0x2560, 0x2561, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567,
        0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590,
        0x2591, 0x2592, 0x2593, 0x25a0, 0x25b2, 0x25bc, 0x25c6, 0x25ca, 0x25cf, 0x25d7,
        0x2605, 0x260e, 0x261b, 0x261e, 0x2660, 0x2663, 0x2665, 0x2666, 0x2701, 0x2702,
        0x2703, 0x2704, 0x2706, 0x2707, 0x2708, 0x2709, 0x270c, 0x270d, 0x270e, 0x270f,
        0x2710, 0x2711, 0x2712, 0x2713, 0x2714, 0x2715, 0x2716, 0x2717, 0x2718, 0x2719,
        0x271a, 0x271b, 0x271c, 0x271d, 0x271e, 0x271f, 0x2720, 0x2721, 0x2722, 0x2723,
        0x2724, 0x2725, 0x2726, 0x2727, 0x2729, 0x272a, 0x272b, 0x272c, 0x272d, 0x272e,
        0x272f, 0x2730, 0x2731, 0x2732, 0x2733, 0x2734, 0x2735, 0x2736, 0x2737, 0x2738,
        0x2739, 0x273a, 0x273b, 0x273c, 0x273d, 0x273e, 0x273f, 0x2740, 0x2741, 0x2742,
        0x2743, 0x2744, 0x2745, 0x2746, 0x2747, 0x2748, 0x2749, 0x274a, 0x274b, 0x274d,
        0x274f, 0x2750, 0x2751, 0x2752, 0x2756, 0x2758, 0x2759, 0x275a, 0x275b, 0x275c,
        0x275d, 0x275e, 0x2761, 0x2762, 0x2763, 0x2764, 0x2765, 0x2766, 0x2767, 0x277f,
        0x2789, 0x2793, 0x2794, 0x2798, 0x2799, 0x279a, 0x279b, 0x279c, 0x279d, 0x279e,
        0x279f, 0x27a0, 0x27a1, 0x27a2, 0x27a3, 0x27a4, 0x27a5, 0x27a6, 0x27a7, 0x27a8,
        0x27a9, 0x27aa, 0x27ab, 0x27ac, 0x27ad, 0x27ae, 0x27af, 0x27b1, 0x27b2, 0x27b3,
        0x27b4, 0x27b5, 0x27b6, 0x27b7, 0x27b8, 0x27b9, 0x27ba, 0x27bb, 0x27bc, 0x27bd,
        0x27be, 0xf6d9, 0xf6da, 0xf6db, 0xf8d7, 0xf8d8, 0xf8d9, 0xf8da, 0xf8db, 0xf8dc,
        0xf8dd, 0xf8de, 0xf8df, 0xf8e0, 0xf8e1, 0xf8e2, 0xf8e3, 0xf8e4, 0xf8e5, 0xf8e6,
        0xf8e7, 0xf8e8, 0xf8e9, 0xf8ea, 0xf8eb, 0xf8ec, 0xf8ed, 0xf8ee, 0xf8ef, 0xf8f0,
        0xf8f1, 0xf8f2, 0xf8f3, 0xf8f4, 0xf8f5, 0xf8f6, 0xf8f7, 0xf8f8, 0xf8f9, 0xf8fa,
        0xf8fb, 0xf8fc, 0xf8fd, 0xf8fe, 0xfe7c, 0xfe7d,
);
***/

    #таблица конвертации регистра
    static $trans = array(
        #CASE_UPPER => CASE_LOWER
        #en (английский латиница)
        "\x41" => "\x61", #a
        "\x42" => "\x62", #b
        "\x43" => "\x63", #c
        "\x44" => "\x64", #d
        "\x45" => "\x65", #e
        "\x46" => "\x66", #f
        "\x47" => "\x67", #g
        "\x48" => "\x68", #h
        "\x49" => "\x69", #i
        "\x4a" => "\x6a", #j
        "\x4b" => "\x6b", #k
        "\x4c" => "\x6c", #l
        "\x4d" => "\x6d", #m
        "\x4e" => "\x6e", #n
        "\x4f" => "\x6f", #o
        "\x50" => "\x70", #p
        "\x51" => "\x71", #q
        "\x52" => "\x72", #r
        "\x53" => "\x73", #s
        "\x54" => "\x74", #t
        "\x55" => "\x75", #u
        "\x57" => "\x77", #w
        "\x56" => "\x76", #v
        "\x58" => "\x78", #x
        "\x59" => "\x79", #y
        "\x5a" => "\x7a", #z

        #ru (русский кириллица)
        "\xd0\x81" => "\xd1\x91", #ё
        "\xd0\x90" => "\xd0\xb0", #а
        "\xd0\x91" => "\xd0\xb1", #б
        "\xd0\x92" => "\xd0\xb2", #в
        "\xd0\x93" => "\xd0\xb3", #г
        "\xd0\x94" => "\xd0\xb4", #д
        "\xd0\x95" => "\xd0\xb5", #е
        "\xd0\x96" => "\xd0\xb6", #ж
        "\xd0\x97" => "\xd0\xb7", #з
        "\xd0\x98" => "\xd0\xb8", #и
        "\xd0\x99" => "\xd0\xb9", #й
        "\xd0\x9a" => "\xd0\xba", #к
        "\xd0\x9b" => "\xd0\xbb", #л
        "\xd0\x9c" => "\xd0\xbc", #м
        "\xd0\x9d" => "\xd0\xbd", #н
        "\xd0\x9e" => "\xd0\xbe", #о
        "\xd0\x9f" => "\xd0\xbf", #п

        "\xd0\xa0" => "\xd1\x80", #р
        "\xd0\xa1" => "\xd1\x81", #с
        "\xd0\xa2" => "\xd1\x82", #т
        "\xd0\xa3" => "\xd1\x83", #у
        "\xd0\xa4" => "\xd1\x84", #ф
        "\xd0\xa5" => "\xd1\x85", #х
        "\xd0\xa6" => "\xd1\x86", #ц
        "\xd0\xa7" => "\xd1\x87", #ч
        "\xd0\xa8" => "\xd1\x88", #ш
        "\xd0\xa9" => "\xd1\x89", #щ
        "\xd0\xaa" => "\xd1\x8a", #ъ
        "\xd0\xab" => "\xd1\x8b", #ы
        "\xd0\xac" => "\xd1\x8c", #ь
        "\xd0\xad" => "\xd1\x8d", #э
        "\xd0\xae" => "\xd1\x8e", #ю
        "\xd0\xaf" => "\xd1\x8f", #я

        #tt (татарский, башкирский кириллица)
        "\xd2\x96" => "\xd2\x97", #ж с хвостиком    &#1174; => &#1175;
        "\xd2\xa2" => "\xd2\xa3", #н с хвостиком    &#1186; => &#1187;
        "\xd2\xae" => "\xd2\xaf", #y                &#1198; => &#1199;
        "\xd2\xba" => "\xd2\xbb", #h мягкое         &#1210; => &#1211;
        "\xd3\x98" => "\xd3\x99", #э                &#1240; => &#1241;
        "\xd3\xa8" => "\xd3\xa9", #o перечеркнутое  &#1256; => &#1257;

        #uk (украинский кириллица)
        "\xd2\x90" => "\xd2\x91",  #г с хвостиком
        "\xd0\x84" => "\xd1\x94",  #э зеркальное отражение
        "\xd0\x86" => "\xd1\x96",  #и с одной точкой
        "\xd0\x87" => "\xd1\x97",  #и с двумя точками

        #be (белорусский кириллица)
        "\xd0\x8e" => "\xd1\x9e",  #у с подковой над буквой

        #tr,de,es (турецкий, немецкий, испанский, французский латиница)
        "\xc3\x84" => "\xc3\xa4", #a умляут          &#196; => &#228;  (турецкий)
        "\xc3\x87" => "\xc3\xa7", #c с хвостиком     &#199; => &#231;  (турецкий, французский)
        "\xc3\x91" => "\xc3\xb1", #n с тильдой       &#209; => &#241;  (турецкий, испанский)
        "\xc3\x96" => "\xc3\xb6", #o умляут          &#214; => &#246;  (турецкий)
        "\xc3\x9c" => "\xc3\xbc", #u умляут          &#220; => &#252;  (турецкий, французский)
        "\xc4\x9e" => "\xc4\x9f", #g умляут          &#286; => &#287;  (турецкий)
        "\xc4\xb0" => "\xc4\xb1", #i c точкой и без  &#304; => &#305;  (турецкий)
        "\xc5\x9e" => "\xc5\x9f", #s с хвостиком     &#350; => &#351;  (турецкий)

        #hr (хорватский латиница)
        "\xc4\x8c" => "\xc4\x8d",  #c с подковой над буквой
        "\xc4\x86" => "\xc4\x87",  #c с ударением
        "\xc4\x90" => "\xc4\x91",  #d перечеркнутое
        "\xc5\xa0" => "\xc5\xa1",  #s с подковой над буквой
        "\xc5\xbd" => "\xc5\xbe",  #z с подковой над буквой

        #fr (французский латиница)
        "\xc3\x80" => "\xc3\xa0",  #a с ударением в др. сторону
        "\xc3\x82" => "\xc3\xa2",  #a с крышкой
        "\xc3\x86" => "\xc3\xa6",  #ae совмещенное
        "\xc3\x88" => "\xc3\xa8",  #e с ударением в др. сторону
        "\xc3\x89" => "\xc3\xa9",  #e с ударением
        "\xc3\x8a" => "\xc3\xaa",  #e с крышкой
        "\xc3\x8b" => "\xc3\xab",  #ё
        "\xc3\x8e" => "\xc3\xae",  #i с крышкой
        "\xc3\x8f" => "\xc3\xaf",  #i умляут
        "\xc3\x94" => "\xc3\xb4",  #o с крышкой
        "\xc5\x92" => "\xc5\x93",  #ce совмещенное
        "\xc3\x99" => "\xc3\xb9",  #u с ударением в др. сторону
        "\xc3\x9b" => "\xc3\xbb",  #u с крышкой
        "\xc5\xb8" => "\xc3\xbf",  #y умляут

        #xx (другой язык)
        #"" => "",  #

    );
    #d($trans);

    #вариант с str_replace() должен работать быстрее, чем с strtr()
    if ($mode == CASE_UPPER)
    {
        if (function_exists('mb_strtoupper')) return mb_strtoupper($s, 'utf-8');
        if (preg_match('/^[\x00-\x7e]*$/', $s)) return strtoupper($s); #может, так быстрее? (:TODO:)
        strtr($s, array_flip($trans));
        #$s = str_replace(array_values($trans), array_keys($trans), $s);
    }
    elseif ($mode == CASE_LOWER)
    {
        if (function_exists('mb_strtolower')) return mb_strtolower($s, 'utf-8');
        if (preg_match('/^[\x00-\x7e]*$/', $s)) return strtolower($s); #может, так быстрее? (:TODO:)
        strtr($s, $trans);
        #$s = str_replace(array_keys($trans), array_values($trans), $s);
    }
    else
    {
        trigger_error('Parameter 2 should be a constant of CASE_LOWER or CASE_UPPER!', E_USER_WARNING);
        return $s;
    }

    return $s;
}

function utf8_lowercase($s)
{
    return utf8_convert_case($s, CASE_LOWER);
}

function utf8_uppercase($s)
{
    return utf8_convert_case($s, CASE_UPPER);
}

/**
 * Преобразует в верхний регистр первый символ каждого слова в строке в кодировке UTF-8,
 * остальные символы каждого слова преобразуются в нижний регистр.
 * Эта функция считает словами последовательности символов, разделенных пробелом, переводом строки, возвратом каретки, горизонтальной табуляцией, неразрывным пробелом.
 *
 * @param   string    $s
 * @return  string
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  1.0.0
 */
function utf8_ucwords($s)
{
    if (! function_exists('utf8_ucfirst')) include_once 'utf8_ucfirst.php'; #оптимизация скорости include_once
    $words = preg_split('/([\x20\r\n\t]+|\xc2\xa0)/s', $s, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    foreach ($words as $k => $word)
    {
        $words[$k] = utf8_ucfirst($word);
    }
    return implode($words);
}

/**
 * Implementation substr() function for utf-8 encoding string.
 *
 * @param    string  $str
 * @param    int     $offset
 * @param    int     $length
 * @return   string
 * @link     http://www.w3.org/International/questions/qa-forms-utf-8.html
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  1.0.3
 */
function utf8_substr($str, $offset, $length = null)
{
    #в начале пробуем найти стандартные функции
    if (function_exists('iconv_substr')) return iconv_substr($str, $offset, $length, 'utf-8'); #(PHP 5)
    if (function_exists('mb_substr')) return mb_substr($str, $offset, $length, 'utf-8'); #(PHP 4 >= 4.0.6, PHP 5)
    #однократные паттерны повышают производительность!
    preg_match_all('/(?>[\x09\x0A\x0D\x20-\x7E]           # ASCII
                      | [\xC2-\xDF][\x80-\xBF]            # non-overlong 2-byte
                      |  \xE0[\xA0-\xBF][\x80-\xBF]       # excluding overlongs
                      | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
                      |  \xED[\x80-\x9F][\x80-\xBF]       # excluding surrogates
                      |  \xF0[\x90-\xBF][\x80-\xBF]{2}    # planes 1-3
                      | [\xF1-\xF3][\x80-\xBF]{3}         # planes 4-15
                      |  \xF4[\x80-\x8F][\x80-\xBF]{2}    # plane 16
                     )
                    /xs', $str, $m);
    if ($length !== null) $a = array_slice($m[0], $offset, $length);
    else $a = array_slice($m[0], $offset);
    return implode('', $a);
}

/**
 * Implementation strlen() function for utf-8 encoding string.
 * There is a nice hack via the utf8_decode function.
 *
 * @param    string  $str
 * @return   int
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   <chernyshevsky at hotmail dot com>
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  1.0.2
 */
function utf8_strlen($str)
{
    if (function_exists('mb_strlen')) return mb_strlen($str, 'utf-8');
    if (function_exists('iconv_strlen')) return iconv_strlen($str, 'utf-8');
    #utf8_decode() converts characters that are not in ISO-8859-1 to '?', which, for the purpose of counting, is quite alright.
    return strlen(utf8_decode($str));
}

/**
 * Обрезает текст в кодировке UTF-8 до заданной длины,
 * причём последнее слово показывается целиком, а не обрывается на середине.
 * Html сущности корректно обрабатываются.
 *
 * @param    string               $s          текст в кодировке UTF-8
 * @param    int                  $maxlength  ограничение длины текста
 * @param    string               $continue   завершающая строка, которая будет вставлена после текста, если он обрежется
 * @return   string
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  3.3.2
 */
function utf8_str_limit($s, $maxlength = 256, $continue = "\xe2\x80\xa6") #"\xe2\x80\xa6" = "&hellip;"
{
    #оптимизация скорости:
    #{{{
    if (strlen($s) <= $maxlength) return $s;
    $s2 = str_replace("\r\n", '?', $s);
    $s2 = preg_replace('/&(?> [a-zA-Z][a-zA-Z\d]+
                            | \#(?> \d{1,4}
                                  | x[\da-fA-F]{2,4}
                                )
                          );  # html сущности (&lt; &gt; &amp; &quot;)
                        /sx', '?', $s2);
    #utf8_decode() converts characters that are not in ISO-8859-1 to '?', which, for the purpose of counting, is quite alright.
    if (strlen($s2) <= $maxlength || strlen(utf8_decode($s2)) <= $maxlength) return $s;
    #}}}

    preg_match_all('/(?> \r\n   # переносы строк
                       | &(?> [a-zA-Z][a-zA-Z\d]+
                            | \#(?> \d{1,4}
                                  | x[\da-fA-F]{2,4}
                                )
                          );  # html сущности (&lt; &gt; &amp; &quot;)
                       | [\x09\x0A\x0D\x20-\x7E]           # ASCII
                       | [\xC2-\xDF][\x80-\xBF]            # non-overlong 2-byte
                       |  \xE0[\xA0-\xBF][\x80-\xBF]       # excluding overlongs
                       | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
                       |  \xED[\x80-\x9F][\x80-\xBF]       # excluding surrogates
                       |  \xF0[\x90-\xBF][\x80-\xBF]{2}    # planes 1-3
                       | [\xF1-\xF3][\x80-\xBF]{3}         # planes 4-15
                       |  \xF4[\x80-\x8F][\x80-\xBF]{2}    # plane 16
                     )
                    /sx', $s, $m);
    #d($m);
    if (count($m[0]) <= $maxlength) return $s;
    $left = implode('', array_slice($m[0], 0, $maxlength));
    #нельзя вырезать в конце строки символ ";", т.к. он используются в сущностях &xxx;
    $left2 = rtrim($left, " \r\n\t`=\\/~!@#*<>'(_+|\";:.,?!*");
    if (strlen($left) !== strlen($left2)) return $left2 . $continue;
    $right = implode('', array_slice($m[0], $maxlength));
    preg_match('/^(?: [a-zA-Z\d\)\]\}\-\.]  #английские буквы или цифры, закрывающие парные символы, дефис для составных слов, дата, IP-адреса, URL типа www.ya.ru!
                    | \xe2\x80[\x9d\x99]|\xc2\xbb|\xe2\x80\x9c  #закрывающие кавычки
                    | \xc3[\xa4\xa7\xb1\xb6\xbc\x84\x87\x91\x96\x9c]|\xc4[\x9f\xb1\x9e\xb0]|\xc5[\x9f\x9e]  #турецкие
                    | \xd0[\x90-\xbf\x81]|\xd1[\x80-\x8f\x91]   #русские буквы
                    | \xd2[\x96\x97\xa2\xa3\xae\xaf\xba\xbb]|\xd3[\x98\x99\xa8\xa9]  #татарские
                  )+
                /sx', $right, $m);
    #d($m);
    $right = isset($m[0]) ? rtrim($m[0], '.-') : '';
    $s2 = $left . $right;
    if (strlen($s2) !== strlen($s)) $s2 .= $continue;
    return $s2;

    /*
    #:DEPRECATED:
    #Компилятор регулярного выражения выдает ошибку на больших текстах $maxlength > 50!!!
    if (preg_match('/^#получаем все символы с ограничением длины строки
                      (?: \r\n   # переносы строк
                       | &(? > [a-zA-Z][a-zA-Z\d]+
                            | \#(? > \d{1,4}
                                  | x[\da-fA-F]{2,4}
                                )
                          );  # html сущности (&lt; &gt; &amp; &quot;)
                       | [\x09\x0A\x0D\x20-\x7E]           # ASCII
                       | [\xC2-\xDF][\x80-\xBF]            # non-overlong 2-byte
                       |  \xE0[\xA0-\xBF][\x80-\xBF]       # excluding overlongs
                       | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
                       |  \xED[\x80-\x9F][\x80-\xBF]       # excluding surrogates
                       |  \xF0[\x90-\xBF][\x80-\xBF]{2}    # planes 1-3
                       | [\xF1-\xF3][\x80-\xBF]{3}         # planes 4-15
                       |  \xF4[\x80-\x8F][\x80-\xBF]{2}    # plane 16
                      ){1,' . intval($maxlength) . '}
                     #добавляем последние символы, чтобы небыло разрыва слова посередине
                     (? > [a-zA-Z\d\)\]\}\-]                #английские буквы или цифры, закрывающие парные символы, дефис для составных слов!
                         | \xe2\x80[\x9d\x99]|\xc2\xbb|\xe2\x80\x9c  #закрывающие кавычки
                         | \xc3[\xa4\xa7\xb1\xb6\xbc\x84\x87\x91\x96\x9c]|\xc4[\x9f\xb1\x9e\xb0]|\xc5[\x9f\x9e]  #турецкие
                         | \xd0[\x90-\xbf\x81]|\xd1[\x80-\x8f\x91]   #русские буквы
                         | \xd2[\x96\x97\xa2\xa3\xae\xaf\xba\xbb]|\xd3[\x98\x99\xa8\xa9]  #татарские
                     ){0,40}
                    /sx', $s, $m))
    {
        #нельзя вырезать в конце строки символ ";", т.к. он используются в сущностях &xxx;
        return rtrim($m[0], " \r\n\t`=\\/~!@#*<>'(_+|\";:.,?!*") . $continue;
        #return $m[0] . $continue;
    }
    #текст не в кодировке UTF-8!
    trigger_error('Unknown charset !', E_USER_WARNING);
    return false;
    */
}

/**
 * Создает условия для простого поискового SQL запроса, основанного на LIKE и REGEXP.
 * Поисковая фраза и данные в таблице БД д.б. в кодировке UTF-8!
 *
 * LIKE используется для оптимизации скорости (и стоит в условии первым!),
 * REGEXP -- для точного поиска целых вхождений слов и чисел.
 *
 * Синтаксис и примеры поискового запроса:
 *   "квантов* механик*", "сервер 3.4GHz 1024Mb", "смысл* жизни" -- ищутся вхождения всех слов (логическое "И")
 *   "квантов* | механик*" -- ищутся вхождения любого найденного (логическое "ИЛИ")
 *   Необязательный символ "*" заменяет ноль или более букв и может стоять только в конце слова!
 *   В начале или в середине слова звёздочка стоять не может!
 *   Регистр слов при поиске не учитывается!
 *
 * @param    string               $s        поисковая фраза
 * @param    array                &$words   массив поисковых слов из запроса (слова и цифры)
 *                                          может понадобится, например, при "подсвечивании" найденных слов в тексте
 * @param    mixed(string/array)  $field    поле или массив полей, по которым будет производиться поиск
 * @return   mixed(string/false)  SQL код для условия WHERE, имя поля обозначается через {field}
 *                                возвращает false в случае ошибки в синтаксисе поискового запроса
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  2.0.5
 */
function utf8_simple_search_sql($s, array &$words, $field = '{field}')
{
    #хак для MySQL для поиска целых слов в кодировке UTF-8 через REGEXP
    #английский + турецкие буквы (татарский латиница)
    #русский + татарские буквы (кириллица)

    #предыдущий/последующий символ -- не буква (lowercase):
    #/*static*/ $re_utf8_no_letter_begin_lc = "([^a-z\x80-\x8f\x91\x97\x99\x9f\xa3\xa4\xa7\xa9\xaf\xb0-\xbf]|\xc3[^\xa4\xa7\xb1\xb6\xbc]|\xc4[^\x9f\xb1]|\xc5[^\x9f]|\xd0[^\xb0-\xbf]|\xd1[^\x80-\x8f\x91]|\xd2[^\x97\xa3\xaf\xbb]|\xd3[^\x99\xa9]|^)";
    #/*static*/ $re_utf8_no_letter_end_lc   = "([^a-z\xc3-\xc5\xd0-\xd3]|\xc3[^\xa4\xa7\xb1\xb6\xbc]|\xc4[^\x9f\xb1]|\xc5[^\x9f]|\xd0[^\xb0-\xbf]|\xd1[^\x80-\x8f\x91]|\xd2[^\x97\xa3\xaf\xbb]|\xd3[^\x99\xa9]|$)";

    #предыдущий/последующий символ -- не буква (all):
    /*static*/ $re_utf8_no_letter_begin = "([^a-zA-Z\x80-\xbf]|\xc3[^\xa4\xa7\xb1\xb6\xbc\x84\x87\x91\x96\x9c]|\xc4[^\x9f\xb1\x9e\xb0]|\xc5[^\x9f\x9e]|\xd0[^\x90-\xbf\x81]|\xd1[^\x80-\x8f\x91]|\xd2[^\x96\x97\xa2\xa3\xae\xaf\xba\xbb]|\xd3[^\x98\x99\xa8\xa9]|^)";
    /*static*/ $re_utf8_no_letter_end   = "([^a-zA-Z\xc3-\xc5\xd0-\xd3]|\xc3[^\xa4\xa7\xb1\xb6\xbc\x84\x87\x91\x96\x9c]|\xc4[^\x9f\xb1\x9e\xb0]|\xc5[^\x9f\x9e]|\xd0[^\x90-\xbf\x81]|\xd1[^\x80-\x8f\x91]|\xd2[^\x96\x97\xa2\xa3\xae\xaf\xba\xbb]|\xd3[^\x98\x99\xa8\xa9]|$)";

    #буквы алфавитов
    static $re_letters = array(
        #английский + турецкие буквы (татарский латиница)
        'tr'    => '[a-zA-Z]|\xc3[\xa4\xa7\xb1\xb6\xbc\x84\x87\x91\x96\x9c]|\xc4[\x9f\xb1\x9e\xb0]|\xc5[\x9f\x9e]', #(all)
        'tr_uc' => '[A-Z]|\xc3[\x84\x87\x91\x96\x9c]|\xc4[\x9e\xb0]|\xc5\x9e', #(uppercase)
        'tr_lc' => '[a-z]|\xc3[\xa4\xa7\xb1\xb6\xbc]|\xc4[\x9f\xb1]|\xc5\x9f', #(lowercase)

        #русский + татарские буквы (кириллица)
        'tt'    => '\xd0[\x90-\xbf\x81]|\xd1[\x80-\x8f\x91]                          #А-я (all)
                   |\xd2[\x96\x97\xa2\xa3\xae\xaf\xba\xbb]|\xd3[\x98\x99\xa8\xa9]',  #татарские буквы (all)
        'tt_uc' => '\xd0[\x90-\xaf\x81]                      #А-Я (uppercase)
                   |\xd2[\x96\xa2\xae\xba]|\xd3[\x98\xa8]',  #татарские буквы (uppercase)
        'tt_lc' => '\xd0[\xb0-\xbf]|\xd1[\x80-\x8f\x91]      #а-я (lowercase)
                   |\xd2[\x97\xa3\xaf\xbb]|\xd3[\x99\xa9]',  #татарские буквы (lowercase)
    );

    #заменяем недопустимые символы из таблицы ASCII на пробел (за исключением " ", "|", "*")
    $s = preg_replace('/(?![\x20\x7c\x2a])[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]/s', ' ', $s);
    $trans = array(
        "\xc2\xad" => '',    #вырезаем "мягкие" переносы строк (&shy;)
        "\xcc\x81" => '',    #знак ударения  (U+0301 «combining acute accent»)
        "\xc2\xa0" => ' ',   #неразрывный пробел
    );
    $s = strtr($s, $trans);
    #$s = str_replace(array_keys($trans), array_values($trans), trim($s, ' |'));
    if (! function_exists('utf8_convert_case')) include_once 'utf8_convert_case.php'; #оптимизация скорости include_once
    $s = utf8_convert_case($s, CASE_LOWER);

    #проверка синтаксиса поискового запроса:
    if (! preg_match('/^(?:(?:(?>' . $re_letters['tr_lc'] . ')+  #слова английские или турецкие
                            | (?>' . $re_letters['tt_lc'] . ')+  #слова русские или татарские
                            | \d+
                            )(?>\*?\x20)?
                            \x20*(?>\|\x20*)?
                         )+
                       $/sx', $s . ' ')) return false; #неверный поисковый запрос
    #d($s);
    if (! function_exists('utf8_ucfirst')) include_once 'utf8_ucfirst.php'; #оптимизация скорости include_once
    preg_match_all('/  ((?>' . $re_letters['tr_lc'] . ')+\*?)  #1 слова английские или турецкие
                     | ((?>' . $re_letters['tt_lc'] . ')+\*?)  #2 слова русские или татарские
                     | (\d+\*?)  #3 цифры
                    /sx', $s, $m);
    $words = array_unique($m[0]);

    $cond = (strpos($s, '|') === false) ? 'AND' : 'OR';
    $q = array();
    foreach ($words as $i => $word)
    {
        if ($is_mask = (substr($word, -1) === '*')) $word = rtrim($word, '*');

        if (strlen($m[3][$i]) > 0)  #для цифр
        {
            $re_word = '([^0-9]|^)' . $word . ($is_mask ? '' : '([^0-9]|$)');
            $like    = '{field} LIKE   BINARY "%' . $word    . '%"';
            $regexp  = '{field} REGEXP BINARY  "' . $re_word . '"';
        }
        else  #для букв
        {
            $word_cases = array(
                'lowercase' => $word,                                 #word
                'ucfirst'   => utf8_ucfirst($word),                   #Word
                'uppercase' => utf8_convert_case($word, CASE_UPPER),  #WORD
            );
            $like = '( {field} LIKE BINARY 0x' . bin2hex('%' . $word_cases['lowercase'] . '%') . ' OR
                       {field} LIKE BINARY 0x' . bin2hex('%' . $word_cases['ucfirst']   . '%') . ' OR
                       {field} LIKE BINARY 0x' . bin2hex('%' . $word_cases['uppercase'] . '%') . ' )';
            $re_word = $re_utf8_no_letter_begin . '(' . implode('|', $word_cases) . ')' . ($is_mask ? '' : $re_utf8_no_letter_end);
            $regexp = '{field} REGEXP BINARY 0x' . bin2hex($re_word);
        }
        #:DEBUG:
        #d(array('like' => $like, 'regexp' => $regexp));

        if ($cond == 'AND')
        {
            $q['like'][]   = $like;
            $q['regexp'][] = $regexp;
        }
        else $q[] = '(' . $like . ' AND ' . $regexp . ')';
    }#foreach

    $r = '(' . implode("\r\n" . $cond . ' ', ($cond == 'AND') ? array_merge($q['like'], $q['regexp']) : $q) . ')';

    $a = array();
    $fields = is_array($field) ? $field : array($field);
    foreach ($fields as $k => $v) $a[] = str_replace('{field}', $v, $r);
    return '(' . implode(' OR ', $a) . ')';
}

/**
 * Convert PHP scalar, array or hash to JS scalar/array/hash.
 *
 * В PHP/5.2.0 появилась функция json_encode(), но выбор php2js() предпочтительнее:
 *   1) работает не только с UTF-8, но и с любыми другими однобайтовыми кодировками (например: windows-1251, koi8-r)
 *   2) умеет конвертировать числа, представленные строковом типе данных в соотв. числовые типы данных (опция)
 *   3) корректно записывает код в javascript для размещения в html между тагами <script></script>
 *
 * @param    mixed    $a
 * @param    char     $quote               символ квотирования (`'` или `"`)
 * @param    bool     $is_convert_numeric  числа, представленные строковом типе данных и конвертируем в соотв. числовые типы данных
 *                                         применяется, когда, например, XML парсер возвращает числа как строки
 *
 * @link     http://www.json.org/
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Dmitry Koterov, Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  2.0.1
 */
function php2js($a, $quote = "'", $is_convert_numeric = false, $_is_key = false)
{
    if (is_null($a)) return strlen($quote) == 0 ? '#DATA_NULL' : 'null';
    if ($a === false) return 'false';
    if ($a === true)  return 'true';
    if (is_scalar($a))
    {
        if ( (! $_is_key && ! $is_convert_numeric && ! is_string($a))
             ||
             #числа, представленные строковом типе данных и конвертируем в соотв. числовые типы данных
             #стандартная функция is_numeric() не подходит, см. документацию
             (! $_is_key && $is_convert_numeric && (is_int($a) || is_float($a) || ctype_digit($a) || preg_match('/^-?(?!=0)\d+(?>\.\d+)?(?>[eE]\d+)?$/s', $a)))
           ) return $a;
        $a = str_replace(array("\r", "\n"), array('\r', '\n'), addslashes($a));
        #исправляем ошибку парсера javascript браузеров: они ищут сигнатуру "</script>" в не зависимости от контекста!
        $a = preg_replace('/<\/(script)/i', '<\\x2f$1', $a);
        return $quote . $a . $quote;
    }
    if (! is_array($a) || ! strlen($quote))
    {
        $a = '#DATA_' . gettype($a);  #"молчаливое" исключение
        return $quote . $a . $quote;
    }
    $isList = true;
    for ($i = 0, reset($a); $i < count($a); $i++, next($a))
    {
        if (key($a) !== $i)
        {
            $isList = false;
            break;
        }
    }
    $result = array();
    if ($isList)
    {
        foreach ($a as $v) $result[] = php2js($v, $quote, $is_convert_numeric);
        return '[' . join(',', $result) . ']';
    }
    else
    {
        #в IE-5.01 есть ошибка: нельзя ставить числа как ключи хэша, д.б. строка!
        foreach ($a as $k => $v)
        {
            $result[] = php2js($k, "'", $is_convert_numeric, true) . ':' . php2js($v, $quote, $is_convert_numeric);
        }
        return '{' . join(',', $result) . '}';
    }
}

/**
 * Более продвинутый аналог strip_tags() для корректного вырезания тагов из html кода.
 * Функция strip_tags(), в зависимости от контекста, может работать не корректно.
 * Воможности:
 *   - корректно обрабатываются вхождения типа "a < b > c"
 *   - корректно обрабатывается "грязный" html, когда в значениях атрибутов тагов могут встречаться символы < >
 *   - корректно обрабатывается разбитый html
 *   - вырезаются комментарии, скрипты, стили, PHP, Perl, ASP код, MS Word таги, CDATA
 *   - автоматически форматируется текст, если он содержит html код
 *   - защита от подделок типа: "<<fake>script>alert('hi')</</fake>script>"
 *
 * @param   string  $s
 * @param   array   $allowable_tags     Массив тагов, которые не будут вырезаны
 * @param   bool    $is_format_spaces   Форматировать пробелы и переносы строк?
 *                                      Текст форматируется автоматически, если он содержит html код:
 *                                      вид текста на выходе (plain) максимально приближеется виду текста в браузере на входе
 *                                      Другими словами, грамотно преобразует text/html в text/plain
 *                                      Если текст содержит html таги, $is_format_spaces = TRUE
 * @param   array   $pair_tags   массив имён парных тагов, которые будут удалены вместе с содержимым
 *                               см. значения по умолчанию
 * @param   array   $para_tags   массив имён парных тагов, которые будут восприниматься как параграфы (если $is_format_spaces = true)
 *                               см. значения по умолчанию
 * @return  string
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  4.0.5
 */
function strip_tags_smart(
    /*string*/ $s,
    array $allowable_tags = null,
    /*boolean*/ $is_format_spaces = false,
    array $pair_tags = array('script', 'style', 'map', 'iframe', 'frameset', 'object', 'applet', 'comment', 'button'),
    array $para_tags = array('p', 'td', 'th', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'form', 'title', 'pre', 'textarea')
)
{
    static $_callback_type  = false;
    static $_allowable_tags = array();
    static $_para_tags      = array();
    #регулярное выражение для атрибутов тагов
    #корректно обрабатывает грязный и битый HTML в однобайтовой или UTF-8 кодировке!
    static $re_attrs_fast_safe =  '(?> (?>[\x20\r\n\t]+|\xc2\xa0)+  #пробельные символы (д.б. обязательно)
                                       (?>
                                         #правильные атрибуты
                                                                        [^>"\']+
                                         | (?<=[\=\x20\r\n\t]|\xc2\xa0) "[^"]*"
                                         | (?<=[\=\x20\r\n\t]|\xc2\xa0) \'[^\']*\'
                                         #разбитые атрибуты
                                         |                              [^>]+
                                       )*
                                   )?';

    if (is_array($s))
    {
        if ($_callback_type === 'strip_tags')
        {
            $tag = strtolower($s[1]);
            if ($_allowable_tags &&
                (array_key_exists($tag, $_allowable_tags) || array_key_exists('<' . trim(strtolower($s[0]), '< />') . '>', $_allowable_tags))
                ) return $s[0];
            if ($tag == 'br') return "\r\n";
            if ($_para_tags && array_key_exists($tag, $_para_tags)) return "\r\n\r\n";
            return '';
        }
        if ($_callback_type === 'strip_spaces')
        {
            if (substr($s[0], 0, 1) === '<') return $s[0];
            return ' ';
        }
        trigger_error('Unknown callback type "' . $_callback_type . '"!', E_USER_ERROR);
    }

    if (($pos = strpos($s, '<')) === false || strpos($s, '>', $pos) === false)  #оптимизация скорости
    {
        #таги не найдены
        return $s;
    }

    #непарные таги (открывающие, закрывающие, !DOCTYPE, MS Word namespace)
    $re_tags = '/<[\/\!]? ([a-zA-Z][a-zA-Z\d]* (?>\:[a-zA-Z][a-zA-Z\d]*)?)' . $re_attrs_fast_safe . '\/?>/sx';

    $patterns = array(
        '/<([\?\%]) .*? \\1>/sx',     #встроенный PHP, Perl, ASP код
        '/<\!\[CDATA\[ .*? \]\]>/sx', #блоки CDATA
        #'/<\!\[  [\x20\r\n\t]* [a-zA-Z] .*?  \]>/sx',  #:DEPRECATED: MS Word таги типа <![if! vml]>...<![endif]>

        '/<\!--.*?-->/s', #комментарии

        #MS Word таги типа "<![if! vml]>...<![endif]>",
        #условное выполнение кода для IE типа "<!--[if expression]> HTML <![endif]-->"
        #условное выполнение кода для IE типа "<![if expression]> HTML <![endif]>"
        #см. http://www.tigir.com/comments.htm
        '/<\! (?>--)?
              \[
              (?> [^\]"\']+ | "[^"]*" | \'[^\']*\' )*
              \]
              (?>--)?
         >/sx',
    );
    if ($pair_tags)
    {
        #парные таги вместе с содержимым:
        foreach ($pair_tags as $k => $v) $pair_tags[$k] = preg_quote($v, '/');
        $patterns[] = '/<((?i:' . implode('|', $pair_tags) . '))' . $re_attrs_fast_safe . '> .*? <\/(?i:\\1)' . $re_attrs_fast_safe . '>/sx';
    }
    #d($patterns);

    $i = 0; #защита от зацикливания
    $max = 99;
    while ($i < $max)
    {
        $s2 = preg_replace($patterns, '', $s);
        if ($i == 0)
        {
            $is_html = ($s2 != $s || preg_match($re_tags, $s2));
            if ($is_html)
            {
                #В библиотеке PCRE для PHP \s - это любой пробельный символ, а именно класс символов [\x09\x0a\x0c\x0d\x20\xa0] или, по другому, [\t\n\f\r \xa0]
                #Если \s используется с модификатором /u, то \s трактуется как [\x09\x0a\x0c\x0d\x20]
                #Браузер не делает различия между пробельными символами,
                #друг за другом подряд идущие символы воспринимаются как один
                #$s2 = str_replace(array("\r", "\n", "\t"), ' ', $s2);
                #$s2 = strtr($s2, "\x09\x0a\x0c\x0d", '    ');

                $_callback_type = 'strip_spaces';
                $s2 = preg_replace_callback('/  [\x09\x0a\x0c\x0d]+
                                              | <((?i:pre|textarea))' . $re_attrs_fast_safe . '>
                                                .+?
                                                <\/(?i:\\1)' . $re_attrs_fast_safe . '>
                                             /sx', __FUNCTION__, $s2);
                $_callback_type = false;

                #массив тагов, которые не будут вырезаны
                if ($allowable_tags) $_allowable_tags = array_flip($allowable_tags);

                #парные таги, которые будут восприниматься как параграфы
                if ($para_tags) $_para_tags = array_flip($para_tags);
            }
        }#if

        #обработка тагов
        if ($is_html)
        {
            $_callback_type = 'strip_tags';
            $s2 = preg_replace_callback($re_tags, __FUNCTION__, $s2);
            $_callback_type = false;
        }

        if ($s === $s2) break;
        $s = $s2; $i++;
    }#while
    if ($i >= $max) $s = strip_tags($s); #too many cycles for replace...

    if ($is_format_spaces || $is_html)
    {
        #вырезаем дублирующие пробелы
        $s = preg_replace('/\x20\x20+/s', ' ', trim($s));
        #вырезаем пробелы в начале и в конце строк
        $s = str_replace(array("\r\n\x20", "\x20\r\n"), "\r\n", $s);
        #заменяем 2 и более переносов строк на 2 переноса строк
        $s = preg_replace('/\r\n[\r\n]+/s', "\r\n\r\n", $s);
    }
    return $s;
}

/**
 * Функция пытается определить наличие мата (нецензурных, матерных слов) в html-тексте в кодировке UTF-8.
 *
 * Алгоритм достаточно надежен и быстр, в т.ч. на больших объемах данных.
 * Метод обнаружения мата основывается на корнях и предлогах русского языка, а не на словаре.
 * Слова "лох", "хер", "залупа", "сука" матерными словами не считаются (см. словарь Даля)
 *
 * http://www.google.com/search?q=%F2%EE%EB%EA%EE%E2%FB%E9%20%F1%EB%EE%E2%E0%F0%FC%20%F0%F3%F1%F1%EA%EE%E3%EE%20%EC%E0%F2%E0&ie=cp1251&oe=UTF-8
 * http://www.awd.ru/dic.htm (Толковый словарь русского мата.)
 *
 * @param    string               $s         строка в кодировке UTF-8
 * @param    string               $delta     ширина найденного фрагмента в словах
 *                                           (кол-во слов от матного слова слева и справа, максимально 10)
 * @param    string               $continue  строка, которая будет вставлена в начале в конце фрагмента
 * @return   mixed(false/string/int)         Возвращает FALSE, если мат не обнаружен, иначе фрагмент текста с матерным словом.
 *                                           В случае возникновения ошибки возвращает код ошибки > 0:
 *                                           * PREG_INTERNAL_ERROR
 *                                           * PREG_BACKTRACK_LIMIT_ERROR (see also pcre.backtrack_limit)
 *                                           * PREG_RECURSION_LIMIT_ERROR (see also pcre.recursion_limit)
 *                                           * PREG_BAD_UTF8_ERROR
 * @dependencies strip_tags_smart(), utf8_html_entity_decode(), utf8_convert_case()
 * @since    2005
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  UTF-8
 * @version  3.1.1
 */
function censure($s, $delta = 3, $continue = "\xe2\x80\xa6")
{
    #предлоги русского языка:
    #[всуо]|
    #по|за|на|об|до|от|вы|вс|вз|из|ис|
    #под|про|при|над|низ|раз|рас|воз|вос|
    #пооб|повы|пона|поза|недо|пере|одно|
    #полуза|произ|пораз|много|
    static $pretext = array(
        #1
        '[уyоo]_?        (?=[еёeхx])',        #у, о   (уебать, охуеть)
        '[вvbсc]_?       (?=[хпбмгжxpmgj])',  #в, с   (впиздячить, схуярить)
        '[вvbсc]_?[ъь]_? (?=[еёe])',          #въ, съ (съебаться, въебать)
        'ё_?             (?=[бb])',           #ё      (ёбля)
        #2
        '[вvb]_?[ыi]_?',      #вы
        '[зz3]_?[аa]_?',      #за
        '[нnh]_?[аaеeиi]_?',  #на, не, ни
        '[вvb]_?[сc]_?          (?=[хпбмгжxpmgj])',  #вс (вспизднуть)
        '[оo]_?[тtбb]_?         (?=[хпбмгжxpmgj])',  #от, об
        '[оo]_?[тtбb]_?[ъь]_?   (?=[еёe])',          #отъ, объ
        '[иiвvb]_?[зz3]_?       (?=[хпбмгжxpmgj])',  #[ив]з
        '[иiвvb]_?[зz3]_?[ъь]_? (?=[еёe])',          #[ив]зъ
        '[иi]_?[сc]_?           (?=[хпбмгжxpmgj])',  #ис
        '[пpдdg]_?[оo]_? (?> [бb]_?         (?=[хпбмгжxpmgj])
                           | [бb]_?  [ъь]_? (?=[еёe])
                           | [зz3]_? [аa] _?
                         )?',  #по, до, пообъ, дообъ, поза, доза (двойные символы вырезаются!)
        #3
        '[пp]_?[рr]_?[оoиi]_?',  #пр[ои]
        '[зz3]_?[лl]_?[оo]_?',   #зло (злоебучая)
        '[нnh]_?[аa]_?[дdg]_?         (?=[хпбмгжxpmgj])',  #над
        '[нnh]_?[аa]_?[дdg]_?[ъь]_?   (?=[еёe])',          #надъ
        '[пp]_?[оo]_?[дdg]_?          (?=[хпбмгжxpmgj])',  #под
        '[пp]_?[оo]_?[дdg]_?[ъь]_?    (?=[еёe])',          #подъ
        '[рr]_?[аa]_?[зz3сc]_?        (?=[хпбмгжxpmgj])',  #ра[зс]
        '[рr]_?[аa]_?[зz3сc]_?[ъь]_?  (?=[еёe])',          #ра[зс]ъ
        '[вvb]_?[оo]_?[зz3сc]_?       (?=[хпбмгжxpmgj])',  #во[зс]
        '[вvb]_?[оo]_?[зz3сc]_?[ъь]_? (?=[еёe])',          #во[зс]ъ
        #4
        '[нnh]_?[еe]_?[дdg]_?[оo]_?',    #недо
        '[пp]_?[еe]_?[рr]_?[еe]_?',      #пере
        '[oо]_?[дdg]_?[нnh]_?[оo]_?',    #одно
        '[кk]_?[oо]_?[нnh]_?[оo]_?',     #коно (коноебиться)
        '[мm]_?[уy]_?[дdg]_?[оoаa]_?',   #муд[оа] (мудаёб)
        '[oо]_?[сc]_?[тt]_?[оo]_?',      #осто (остопиздело)
        '[дdg]_?[уy]_?[рpr]_?[оoаa]_?',  #дур[оа]
        '[хx]_?[уy]_?[дdg]_?[оoаa]_?',   #худ[оа] (худоебина)
        #5
        '[мm]_?[нnh]_?[оo]_?[гg]_?[оo]_?',    #много
        '[мm]_?[оo]_?[рpr]_?[дdg]_?[оoаa]_?', #морд[оа]
        '[мm]_?[оo]_?[зz3]_?[гg]_?[оoаa]_?',  #мозг[оа]
        '[дdg]_?[оo]_?[лl]_?[бb6]_?[оoаa]_?', #долб[оа]
    );

    static $badwords = array(
        #Слово на букву Х
        '(?<=[_\d]) {RE_PRETEXT}?
         [hхx]_?[уyu]_?[йiеeёяюju]     #хуй, хуя, хую, хуем, хуёвый
         #исключения:
         (?<! _hue(?=_)    #HUE    -- цветовая палитра
            | _hue(?=so_)  #hueso  -- испанское слово
            | _хуе(?=дин)  #Хуедин -- город в Румынии
         )',

        #Слово на букву П
        '(?<=[_\d]) {RE_PRETEXT}?
         [пp]_?[иi]_?[зz3]_?[дd]_?[:vowel:]',  #пизда, пизде, пиздёж, пизду, пиздюлина, пиздобол, опиздинеть, пиздых

        #Слово на букву Е
        '(?<=[_\d]) {RE_PRETEXT}?
         [eеё]_? (?<!не[её]_) [бb6]_?(?: [уyиi]_                       #ебу, еби
                                       | [ыиiоoaаеeёуy]_?[:consonant:] #ебут, ебать, ебись, ебёт, поеботина, выебываться, ёбарь
                                       | [лl][оoаaыиi]                 #ебло, ебла, ебливая, еблись, еблысь
                                       | [нn]_?[уy]                    #ёбнул, ёбнутый
                                       | [кk]_?[аa]                    #взъёбка
                                      )',
        '(?<=[_\d]) {RE_PRETEXT}
         (?<=[^_\d][^_\d]|[^_\d]_[^_\d]_) [eеё]_?[бb6] (?:_|_?[аa]_?[^_\d])',  #долбоёб, дураёб, изъёб, заёб, разъебай

        #Слово на букву Б
        '(?<=[_\d]) {RE_PRETEXT}?
         [бb6]_?[лl]_?(?:я|ya)(?: _         #бля
                                | _?[тдtd]  #блять, бляди
                              )',

        #ПИДОР
        '(?<=[_\d]) [пp]_?[иieе]_?[дdg]_?[eеaаoо]_?[rpр]',  #п[ие]д[оеа]р

        #МУДАК
        '(?<=[_\d]) [мm]_?[уy]_?[дdg]_?[аa]',  #муда

        #ЖОПА
        '(?<=[_\d]) [zж]_?h?_?[оo]_?[pп]_?[aаyуыiеeoо]',  #жоп[ауыео]

        #МАНДА
        #исключения: город Мандалай, округ Мандаль, индейский народ Мандан, фамилия Мандель
        '(?<=[_\d]) [мm]_?[аa]_?[нnh]_?[дdg]_?[aаyуыiеeoо] (?<! манда(?=[лн])|манде(?=ль ))', #манд[ауыео]

        #ГОВНО
        '(?<=[_\d]) [гg]_?[оo]_?[вvb]_?[нnh]_?[оoаaяеeyу]', #говн[оаяеу]

        #FUCK
        '(?<=[_\d]) f_?u_?[cс]_?k',  #fuck

        /*
        #ЛОХ
        ' л_?[оo]_?[хx]',

        #СУКА
        '[^р]_?[scс]_?[yуu]_?[kк]_?[aаiи]', #сука (кроме слова "барсука" - это животное-грызун)
        '[^р]_?[scс]_?[yуu]_?[4ч]_?[кk]',   #сучк(и) (кроме слова "барсучка")

        #ХЕР
        ' {RE_PRETEXT}?[хxh]_?[еe]_?[рpr](_?[нnh]_?(я|ya)| )', #{RE_PRETEXT}хер(ня)

        #ЗАЛУПА
        ' [зz3]_?[аa]_?[лl]_?[уy]_?[пp]_?[аa]',
        */
    );

    static $re_trans = array(
        '_'             => '\x20',                       #пробел
        '[:vowel:]'     => '[аеиоуыэюяёaeioyu]',         #гласные буквы
        '[:consonant:]' => '[^аеиоуыэюяёaeioyu\x20\d]',  #согласные буквы
    );
    $re_badwords = str_replace('{RE_PRETEXT}', 
                               '(?>' . implode('|', $pretext) . ')',
                               '~' . implode('|', $badwords) . '~sxu');
    $re_badwords = strtr($re_badwords, $re_trans);

    #вырезаем все лишнее
    #скрипты не вырезаем, т.к. м.б. обходной маневр на с кодом на javascript:
    #<script>document.write('сло'+'во')</script>
    #хотя давать пользователю возможность использовать код на javascript нехорошо
    if (! function_exists('strip_tags_smart')) include_once 'strip_tags_smart.php'; #оптимизация скорости include_once
    $s = strip_tags_smart($s, null, true, array('comment', 'style', 'map', 'frameset', 'object', 'applet'));

    #заменяем html-сущности в "чистый" UTF-8
    if (! function_exists('utf8_html_entity_decode')) include_once 'utf8_html_entity_decode.php'; #оптимизация скорости include_once
    $s = utf8_html_entity_decode($s, $is_htmlspecialchars = true);

    if (! function_exists('utf8_convert_case')) include_once 'utf8_convert_case.php'; #оптимизация скорости include_once
    $s = utf8_convert_case($s, CASE_LOWER);

    static $trans = array(
        "\xc2\xad" => '',   #вырезаем "мягкий" перенос строки (&shy;)
        "\xcc\x81" => '',   #вырезаем знак ударения (U+0301 «combining acute accent»)
        '/\\'      => 'л',  #Б/\Я
        '/|'       => 'л',  #Б/|Я
        "\xd0\xb5\xd0\xb5" => "\xd0\xb5\xd1\x91",  #ее => её
    );
    $s = strtr($s, $trans);

    #получаем в массив только буквы и цифры
    #"с_л@о#во,с\xc2\xa7лово.Слово" -> "слово слово слово"
    preg_match_all('/(?> \xd0[\xb0-\xbf]|\xd1[\x80-\x8f\x91]  #[а-я]
                      |  [a-z\d]+
                      )+
                    /sx', $s, $m);
    $s = ' ' . implode(' ', $m[0]) . ' ';

    #убираем все повторяющиеся символы
    #"сллоооовоо   слово" -> "слово слово"
    $s = preg_replace('/(  [\xd0\xd1][\x80-\xbf]  #оптимизированное [а-я]
                         | [a-z\d]
                         ) \\1+
                       /sx', '$1', $s);
    #d($s);
    $result = preg_match($re_badwords, $s, $m, PREG_OFFSET_CAPTURE);
    if (function_exists('preg_last_error') && preg_last_error() !== PREG_NO_ERROR) return preg_last_error();
    if ($result === false) return 1; #PREG_INTERNAL_ERROR = 1
    if ($result)
    {
        list($word, $offset) = $m[0];
        $s1 = substr($s, 0, $offset);
        $s2 = substr($s, $offset + strlen($word));
        $delta = intval($delta);
        if ($delta < 1 || $delta > 10) $delta = 3;
        preg_match('/  (?> \x20 (?>[\xd0\xd1][\x80-\xbf]|[a-z\d]+)+ ){1,' . $delta . '}
                       \x20?
                    $/sx', $s1, $m1);
        preg_match('/^ (?>[\xd0\xd1][\x80-\xbf]|[a-z\d]+)*  #окончание
                       \x20?
                       (?> (?>[\xd0\xd1][\x80-\xbf]|[a-z\d]+)+ \x20 ){1,' . $delta . '}
                    /sx', $s2, $m2);
        $fragment = (ltrim(@$m1[0]) !== ltrim($s1) ? $continue : '') .
                    trim(@$m1[0] . '[' . trim($word) . ']' . @$m2[0]) . 
                    (rtrim(@$m2[0]) !== rtrim($s2) ? $continue : '');
        return $fragment;
    }
    return false;
}

/**
 * Convert all HTML entities to native UTF-8 characters
 * Функция декодирует гораздо больше именованных сущностей, чем стандартная html_entity_decode()
 * Все dec и hex сущности так же переводятся в UTF-8.
 *
 * @param    string   $s
 * @param    bool     $is_htmlspecialchars   обрабатывать специальные html сущности? (&lt; &gt; &amp; &quot;)
 * @return   string
 * @link     http://www.htmlhelp.com/reference/html40/entities/
 * @link     http://www.alanwood.net/demos/ent4_frame.html (HTML 4.01 Character Entity References)
 * @link     http://msdn.microsoft.com/workshop/author/dhtml/reference/charsets/charset1.asp?frame=true
 * @link     http://msdn.microsoft.com/workshop/author/dhtml/reference/charsets/charset2.asp?frame=true
 * @link     http://msdn.microsoft.com/workshop/author/dhtml/reference/charsets/charset3.asp?frame=true
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  2.1.12
 */
function utf8_html_entity_decode($s, $is_htmlspecialchars = false)
{
    #оптимизация скорости
    if (strlen($s) < 4  #по минимальной длине сущности - 4 байта: &#d; &xx;
        || ($pos = strpos($s, '&') === false) || strpos($s, ';', $pos) === false) return $s;
    $table = array(
      #Latin-1 Entities:
        '&nbsp;'   => "\xc2\xa0",  #no-break space = non-breaking space
        '&iexcl;'  => "\xc2\xa1",  #inverted exclamation mark
        '&cent;'   => "\xc2\xa2",  #cent sign
        '&pound;'  => "\xc2\xa3",  #pound sign
        '&curren;' => "\xc2\xa4",  #currency sign
        '&yen;'    => "\xc2\xa5",  #yen sign = yuan sign
        '&brvbar;' => "\xc2\xa6",  #broken bar = broken vertical bar
        '&sect;'   => "\xc2\xa7",  #section sign
        '&uml;'    => "\xc2\xa8",  #diaeresis = spacing diaeresis
        '&copy;'   => "\xc2\xa9",  #copyright sign
        '&ordf;'   => "\xc2\xaa",  #feminine ordinal indicator
        '&laquo;'  => "\xc2\xab",  #left-pointing double angle quotation mark = left pointing guillemet («)
        '&not;'    => "\xc2\xac",  #not sign
        '&shy;'    => "\xc2\xad",  #soft hyphen = discretionary hyphen
        '&reg;'    => "\xc2\xae",  #registered sign = registered trade mark sign
        '&macr;'   => "\xc2\xaf",  #macron = spacing macron = overline = APL overbar
        '&deg;'    => "\xc2\xb0",  #degree sign
        '&plusmn;' => "\xc2\xb1",  #plus-minus sign = plus-or-minus sign
        '&sup2;'   => "\xc2\xb2",  #superscript two = superscript digit two = squared
        '&sup3;'   => "\xc2\xb3",  #superscript three = superscript digit three = cubed
        '&acute;'  => "\xc2\xb4",  #acute accent = spacing acute
        '&micro;'  => "\xc2\xb5",  #micro sign
        '&para;'   => "\xc2\xb6",  #pilcrow sign = paragraph sign
        '&middot;' => "\xc2\xb7",  #middle dot = Georgian comma = Greek middle dot
        '&cedil;'  => "\xc2\xb8",  #cedilla = spacing cedilla
        '&sup1;'   => "\xc2\xb9",  #superscript one = superscript digit one
        '&ordm;'   => "\xc2\xba",  #masculine ordinal indicator
        '&raquo;'  => "\xc2\xbb",  #right-pointing double angle quotation mark = right pointing guillemet (»)
        '&frac14;' => "\xc2\xbc",  #vulgar fraction one quarter = fraction one quarter
        '&frac12;' => "\xc2\xbd",  #vulgar fraction one half = fraction one half
        '&frac34;' => "\xc2\xbe",  #vulgar fraction three quarters = fraction three quarters
        '&iquest;' => "\xc2\xbf",  #inverted question mark = turned question mark
      #Latin capital letter
        '&Agrave;' => "\xc3\x80",  #Latin capital letter A with grave = Latin capital letter A grave
        '&Aacute;' => "\xc3\x81",  #Latin capital letter A with acute
        '&Acirc;'  => "\xc3\x82",  #Latin capital letter A with circumflex
        '&Atilde;' => "\xc3\x83",  #Latin capital letter A with tilde
        '&Auml;'   => "\xc3\x84",  #Latin capital letter A with diaeresis
        '&Aring;'  => "\xc3\x85",  #Latin capital letter A with ring above = Latin capital letter A ring
        '&AElig;'  => "\xc3\x86",  #Latin capital letter AE = Latin capital ligature AE
        '&Ccedil;' => "\xc3\x87",  #Latin capital letter C with cedilla
        '&Egrave;' => "\xc3\x88",  #Latin capital letter E with grave
        '&Eacute;' => "\xc3\x89",  #Latin capital letter E with acute
        '&Ecirc;'  => "\xc3\x8a",  #Latin capital letter E with circumflex
        '&Euml;'   => "\xc3\x8b",  #Latin capital letter E with diaeresis
        '&Igrave;' => "\xc3\x8c",  #Latin capital letter I with grave
        '&Iacute;' => "\xc3\x8d",  #Latin capital letter I with acute
        '&Icirc;'  => "\xc3\x8e",  #Latin capital letter I with circumflex
        '&Iuml;'   => "\xc3\x8f",  #Latin capital letter I with diaeresis
        '&ETH;'    => "\xc3\x90",  #Latin capital letter ETH
        '&Ntilde;' => "\xc3\x91",  #Latin capital letter N with tilde
        '&Ograve;' => "\xc3\x92",  #Latin capital letter O with grave
        '&Oacute;' => "\xc3\x93",  #Latin capital letter O with acute
        '&Ocirc;'  => "\xc3\x94",  #Latin capital letter O with circumflex
        '&Otilde;' => "\xc3\x95",  #Latin capital letter O with tilde
        '&Ouml;'   => "\xc3\x96",  #Latin capital letter O with diaeresis
        '&times;'  => "\xc3\x97",  #multiplication sign
        '&Oslash;' => "\xc3\x98",  #Latin capital letter O with stroke = Latin capital letter O slash
        '&Ugrave;' => "\xc3\x99",  #Latin capital letter U with grave
        '&Uacute;' => "\xc3\x9a",  #Latin capital letter U with acute
        '&Ucirc;'  => "\xc3\x9b",  #Latin capital letter U with circumflex
        '&Uuml;'   => "\xc3\x9c",  #Latin capital letter U with diaeresis
        '&Yacute;' => "\xc3\x9d",  #Latin capital letter Y with acute
        '&THORN;'  => "\xc3\x9e",  #Latin capital letter THORN
      #Latin small letter
        '&szlig;'  => "\xc3\x9f",  #Latin small letter sharp s = ess-zed
        '&agrave;' => "\xc3\xa0",  #Latin small letter a with grave = Latin small letter a grave
        '&aacute;' => "\xc3\xa1",  #Latin small letter a with acute
        '&acirc;'  => "\xc3\xa2",  #Latin small letter a with circumflex
        '&atilde;' => "\xc3\xa3",  #Latin small letter a with tilde
        '&auml;'   => "\xc3\xa4",  #Latin small letter a with diaeresis
        '&aring;'  => "\xc3\xa5",  #Latin small letter a with ring above = Latin small letter a ring
        '&aelig;'  => "\xc3\xa6",  #Latin small letter ae = Latin small ligature ae
        '&ccedil;' => "\xc3\xa7",  #Latin small letter c with cedilla
        '&egrave;' => "\xc3\xa8",  #Latin small letter e with grave
        '&eacute;' => "\xc3\xa9",  #Latin small letter e with acute
        '&ecirc;'  => "\xc3\xaa",  #Latin small letter e with circumflex
        '&euml;'   => "\xc3\xab",  #Latin small letter e with diaeresis
        '&igrave;' => "\xc3\xac",  #Latin small letter i with grave
        '&iacute;' => "\xc3\xad",  #Latin small letter i with acute
        '&icirc;'  => "\xc3\xae",  #Latin small letter i with circumflex
        '&iuml;'   => "\xc3\xaf",  #Latin small letter i with diaeresis
        '&eth;'    => "\xc3\xb0",  #Latin small letter eth
        '&ntilde;' => "\xc3\xb1",  #Latin small letter n with tilde
        '&ograve;' => "\xc3\xb2",  #Latin small letter o with grave
        '&oacute;' => "\xc3\xb3",  #Latin small letter o with acute
        '&ocirc;'  => "\xc3\xb4",  #Latin small letter o with circumflex
        '&otilde;' => "\xc3\xb5",  #Latin small letter o with tilde
        '&ouml;'   => "\xc3\xb6",  #Latin small letter o with diaeresis
        '&divide;' => "\xc3\xb7",  #division sign
        '&oslash;' => "\xc3\xb8",  #Latin small letter o with stroke = Latin small letter o slash
        '&ugrave;' => "\xc3\xb9",  #Latin small letter u with grave
        '&uacute;' => "\xc3\xba",  #Latin small letter u with acute
        '&ucirc;'  => "\xc3\xbb",  #Latin small letter u with circumflex
        '&uuml;'   => "\xc3\xbc",  #Latin small letter u with diaeresis
        '&yacute;' => "\xc3\xbd",  #Latin small letter y with acute
        '&thorn;'  => "\xc3\xbe",  #Latin small letter thorn
        '&yuml;'   => "\xc3\xbf",  #Latin small letter y with diaeresis
      #Symbols and Greek Letters:
        '&fnof;'    => "\xc6\x92",  #Latin small f with hook = function = florin
        '&Alpha;'   => "\xce\x91",  #Greek capital letter alpha
        '&Beta;'    => "\xce\x92",  #Greek capital letter beta
        '&Gamma;'   => "\xce\x93",  #Greek capital letter gamma
        '&Delta;'   => "\xce\x94",  #Greek capital letter delta
        '&Epsilon;' => "\xce\x95",  #Greek capital letter epsilon
        '&Zeta;'    => "\xce\x96",  #Greek capital letter zeta
        '&Eta;'     => "\xce\x97",  #Greek capital letter eta
        '&Theta;'   => "\xce\x98",  #Greek capital letter theta
        '&Iota;'    => "\xce\x99",  #Greek capital letter iota
        '&Kappa;'   => "\xce\x9a",  #Greek capital letter kappa
        '&Lambda;'  => "\xce\x9b",  #Greek capital letter lambda
        '&Mu;'      => "\xce\x9c",  #Greek capital letter mu
        '&Nu;'      => "\xce\x9d",  #Greek capital letter nu
        '&Xi;'      => "\xce\x9e",  #Greek capital letter xi
        '&Omicron;' => "\xce\x9f",  #Greek capital letter omicron
        '&Pi;'      => "\xce\xa0",  #Greek capital letter pi
        '&Rho;'     => "\xce\xa1",  #Greek capital letter rho
        '&Sigma;'   => "\xce\xa3",  #Greek capital letter sigma
        '&Tau;'     => "\xce\xa4",  #Greek capital letter tau
        '&Upsilon;' => "\xce\xa5",  #Greek capital letter upsilon
        '&Phi;'     => "\xce\xa6",  #Greek capital letter phi
        '&Chi;'     => "\xce\xa7",  #Greek capital letter chi
        '&Psi;'     => "\xce\xa8",  #Greek capital letter psi
        '&Omega;'   => "\xce\xa9",  #Greek capital letter omega
        '&alpha;'   => "\xce\xb1",  #Greek small letter alpha
        '&beta;'    => "\xce\xb2",  #Greek small letter beta
        '&gamma;'   => "\xce\xb3",  #Greek small letter gamma
        '&delta;'   => "\xce\xb4",  #Greek small letter delta
        '&epsilon;' => "\xce\xb5",  #Greek small letter epsilon
        '&zeta;'    => "\xce\xb6",  #Greek small letter zeta
        '&eta;'     => "\xce\xb7",  #Greek small letter eta
        '&theta;'   => "\xce\xb8",  #Greek small letter theta
        '&iota;'    => "\xce\xb9",  #Greek small letter iota
        '&kappa;'   => "\xce\xba",  #Greek small letter kappa
        '&lambda;'  => "\xce\xbb",  #Greek small letter lambda
        '&mu;'      => "\xce\xbc",  #Greek small letter mu
        '&nu;'      => "\xce\xbd",  #Greek small letter nu
        '&xi;'      => "\xce\xbe",  #Greek small letter xi
        '&omicron;' => "\xce\xbf",  #Greek small letter omicron
        '&pi;'      => "\xcf\x80",  #Greek small letter pi
        '&rho;'     => "\xcf\x81",  #Greek small letter rho
        '&sigmaf;'  => "\xcf\x82",  #Greek small letter final sigma
        '&sigma;'   => "\xcf\x83",  #Greek small letter sigma
        '&tau;'     => "\xcf\x84",  #Greek small letter tau
        '&upsilon;' => "\xcf\x85",  #Greek small letter upsilon
        '&phi;'     => "\xcf\x86",  #Greek small letter phi
        '&chi;'     => "\xcf\x87",  #Greek small letter chi
        '&psi;'     => "\xcf\x88",  #Greek small letter psi
        '&omega;'   => "\xcf\x89",  #Greek small letter omega
        '&thetasym;'=> "\xcf\x91",  #Greek small letter theta symbol
        '&upsih;'   => "\xcf\x92",  #Greek upsilon with hook symbol
        '&piv;'     => "\xcf\x96",  #Greek pi symbol

        '&bull;'    => "\xe2\x80\xa2",  #bullet = black small circle
        '&hellip;'  => "\xe2\x80\xa6",  #horizontal ellipsis = three dot leader
        '&prime;'   => "\xe2\x80\xb2",  #prime = minutes = feet (для обозначения минут и футов)
        '&Prime;'   => "\xe2\x80\xb3",  #double prime = seconds = inches (для обозначения секунд и дюймов).
        '&oline;'   => "\xe2\x80\xbe",  #overline = spacing overscore
        '&frasl;'   => "\xe2\x81\x84",  #fraction slash
        '&weierp;'  => "\xe2\x84\x98",  #script capital P = power set = Weierstrass p
        '&image;'   => "\xe2\x84\x91",  #blackletter capital I = imaginary part
        '&real;'    => "\xe2\x84\x9c",  #blackletter capital R = real part symbol
        '&trade;'   => "\xe2\x84\xa2",  #trade mark sign
        '&alefsym;' => "\xe2\x84\xb5",  #alef symbol = first transfinite cardinal
        '&larr;'    => "\xe2\x86\x90",  #leftwards arrow
        '&uarr;'    => "\xe2\x86\x91",  #upwards arrow
        '&rarr;'    => "\xe2\x86\x92",  #rightwards arrow
        '&darr;'    => "\xe2\x86\x93",  #downwards arrow
        '&harr;'    => "\xe2\x86\x94",  #left right arrow
        '&crarr;'   => "\xe2\x86\xb5",  #downwards arrow with corner leftwards = carriage return
        '&lArr;'    => "\xe2\x87\x90",  #leftwards double arrow
        '&uArr;'    => "\xe2\x87\x91",  #upwards double arrow
        '&rArr;'    => "\xe2\x87\x92",  #rightwards double arrow
        '&dArr;'    => "\xe2\x87\x93",  #downwards double arrow
        '&hArr;'    => "\xe2\x87\x94",  #left right double arrow
        '&forall;'  => "\xe2\x88\x80",  #for all
        '&part;'    => "\xe2\x88\x82",  #partial differential
        '&exist;'   => "\xe2\x88\x83",  #there exists
        '&empty;'   => "\xe2\x88\x85",  #empty set = null set = diameter
        '&nabla;'   => "\xe2\x88\x87",  #nabla = backward difference
        '&isin;'    => "\xe2\x88\x88",  #element of
        '&notin;'   => "\xe2\x88\x89",  #not an element of
        '&ni;'      => "\xe2\x88\x8b",  #contains as member
        '&prod;'    => "\xe2\x88\x8f",  #n-ary product = product sign
        '&sum;'     => "\xe2\x88\x91",  #n-ary sumation
        '&minus;'   => "\xe2\x88\x92",  #minus sign
        '&lowast;'  => "\xe2\x88\x97",  #asterisk operator
        '&radic;'   => "\xe2\x88\x9a",  #square root = radical sign
        '&prop;'    => "\xe2\x88\x9d",  #proportional to
        '&infin;'   => "\xe2\x88\x9e",  #infinity
        '&ang;'     => "\xe2\x88\xa0",  #angle
        '&and;'     => "\xe2\x88\xa7",  #logical and = wedge
        '&or;'      => "\xe2\x88\xa8",  #logical or = vee
        '&cap;'     => "\xe2\x88\xa9",  #intersection = cap
        '&cup;'     => "\xe2\x88\xaa",  #union = cup
        '&int;'     => "\xe2\x88\xab",  #integral
        '&there4;'  => "\xe2\x88\xb4",  #therefore
        '&sim;'     => "\xe2\x88\xbc",  #tilde operator = varies with = similar to
        '&cong;'    => "\xe2\x89\x85",  #approximately equal to
        '&asymp;'   => "\xe2\x89\x88",  #almost equal to = asymptotic to
        '&ne;'      => "\xe2\x89\xa0",  #not equal to
        '&equiv;'   => "\xe2\x89\xa1",  #identical to
        '&le;'      => "\xe2\x89\xa4",  #less-than or equal to
        '&ge;'      => "\xe2\x89\xa5",  #greater-than or equal to
        '&sub;'     => "\xe2\x8a\x82",  #subset of
        '&sup;'     => "\xe2\x8a\x83",  #superset of
        '&nsub;'    => "\xe2\x8a\x84",  #not a subset of
        '&sube;'    => "\xe2\x8a\x86",  #subset of or equal to
        '&supe;'    => "\xe2\x8a\x87",  #superset of or equal to
        '&oplus;'   => "\xe2\x8a\x95",  #circled plus = direct sum
        '&otimes;'  => "\xe2\x8a\x97",  #circled times = vector product
        '&perp;'    => "\xe2\x8a\xa5",  #up tack = orthogonal to = perpendicular
        '&sdot;'    => "\xe2\x8b\x85",  #dot operator
        '&lceil;'   => "\xe2\x8c\x88",  #left ceiling = APL upstile
        '&rceil;'   => "\xe2\x8c\x89",  #right ceiling
        '&lfloor;'  => "\xe2\x8c\x8a",  #left floor = APL downstile
        '&rfloor;'  => "\xe2\x8c\x8b",  #right floor
        '&lang;'    => "\xe2\x8c\xa9",  #left-pointing angle bracket = bra
        '&rang;'    => "\xe2\x8c\xaa",  #right-pointing angle bracket = ket
        '&loz;'     => "\xe2\x97\x8a",  #lozenge
        '&spades;'  => "\xe2\x99\xa0",  #black spade suit
        '&clubs;'   => "\xe2\x99\xa3",  #black club suit = shamrock
        '&hearts;'  => "\xe2\x99\xa5",  #black heart suit = valentine
        '&diams;'   => "\xe2\x99\xa6",  #black diamond suit
      #Other Special Characters:
        '&OElig;'  => "\xc5\x92",  #Latin capital ligature OE
        '&oelig;'  => "\xc5\x93",  #Latin small ligature oe
        '&Scaron;' => "\xc5\xa0",  #Latin capital letter S with caron
        '&scaron;' => "\xc5\xa1",  #Latin small letter s with caron
        '&Yuml;'   => "\xc5\xb8",  #Latin capital letter Y with diaeresis
        '&circ;'   => "\xcb\x86",  #modifier letter circumflex accent
        '&tilde;'  => "\xcb\x9c",  #small tilde
        '&ensp;'   => "\xe2\x80\x82",  #en space
        '&emsp;'   => "\xe2\x80\x83",  #em space
        '&thinsp;' => "\xe2\x80\x89",  #thin space
        '&zwnj;'   => "\xe2\x80\x8c",  #zero width non-joiner
        '&zwj;'    => "\xe2\x80\x8d",  #zero width joiner
        '&lrm;'    => "\xe2\x80\x8e",  #left-to-right mark
        '&rlm;'    => "\xe2\x80\x8f",  #right-to-left mark
        '&ndash;'  => "\xe2\x80\x93",  #en dash
        '&mdash;'  => "\xe2\x80\x94",  #em dash
        '&lsquo;'  => "\xe2\x80\x98",  #left single quotation mark
        '&rsquo;'  => "\xe2\x80\x99",  #right single quotation mark (and apostrophe!)
        '&sbquo;'  => "\xe2\x80\x9a",  #single low-9 quotation mark
        '&ldquo;'  => "\xe2\x80\x9c",  #left double quotation mark
        '&rdquo;'  => "\xe2\x80\x9d",  #right double quotation mark
        '&bdquo;'  => "\xe2\x80\x9e",  #double low-9 quotation mark
        '&dagger;' => "\xe2\x80\xa0",  #dagger
        '&Dagger;' => "\xe2\x80\xa1",  #double dagger
        '&permil;' => "\xe2\x80\xb0",  #per mille sign
        '&lsaquo;' => "\xe2\x80\xb9",  #single left-pointing angle quotation mark
        '&rsaquo;' => "\xe2\x80\xba",  #single right-pointing angle quotation mark
        '&euro;'   => "\xe2\x82\xac",  #euro sign
    );
    $htmlspecialchars = array(
        '&quot;' => "\x22",  #quotation mark = APL quote (") &#34;
        '&amp;'  => "\x26",  #ampersand                  (&) &#38;
        '&lt;'   => "\x3c",  #less-than sign             (<) &#60;
        '&gt;'   => "\x3e",  #greater-than sign          (>) &#62;
    );

    if ($is_htmlspecialchars) $table += $htmlspecialchars;

    #заменяем именованные сущности:
    #оптимизация скорости: заменяем только те сущности, которые используются в html коде!
    #эта часть кода работает быстрее, чем $s = strtr($s, $table);
    preg_match_all('/&[a-zA-Z]+\d*;/s', $s, $m, null, $pos);
    foreach (array_unique($m[0]) as $entity)
    {
        if (array_key_exists($entity, $table)) $s = str_replace($entity, $table[$entity], $s);
    }#foreach

    if (($pos = strpos($s, '&#')) !== false)  #speed optimization
    {
        #заменяем числовые dec и hex сущности:
        $htmlspecialchars_flip = array_flip($htmlspecialchars);
        $s = preg_replace(
            '/&#((x)[\da-fA-F]{2,4}|\d{1,4});/se',
            '(array_key_exists($a = pack("C", $d = ("$2") ? hexdec("$1") : "$1"), $htmlspecialchars_flip) && ! $is_htmlspecialchars) ?
             $htmlspecialchars_flip[$a] :
             iconv("UCS-2BE", "UTF-8", pack("n", $d))', $s, -1, $pos);
    }
    return $s;
}

/**
 * Convert special UTF-8 characters to HTML entities
 * Функция кодирует гораздо больше именованнvх сущностей, чем стандартная htmlentities()
 *
 * @param    string   $s
 * @return   string
 * @link     http://www.htmlhelp.com/reference/html40/entities/
 * @link     http://www.alanwood.net/demos/ent4_frame.html (HTML 4.01 Character Entity References)
 * @link     http://msdn.microsoft.com/workshop/author/dhtml/reference/charsets/charset1.asp?frame=true
 * @link     http://msdn.microsoft.com/workshop/author/dhtml/reference/charsets/charset2.asp?frame=true
 * @link     http://msdn.microsoft.com/workshop/author/dhtml/reference/charsets/charset3.asp?frame=true
 *
 * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>
 * @charset  ANSI
 * @version  1.0.3
 */
function utf8_html_entity_encode($s)
{
    $table = array_flip(array(
      #Latin-1 Entities:
        '&nbsp;'   => "\xc2\xa0",  #no-break space = non-breaking space
        '&iexcl;'  => "\xc2\xa1",  #inverted exclamation mark
        '&cent;'   => "\xc2\xa2",  #cent sign
        '&pound;'  => "\xc2\xa3",  #pound sign
        '&curren;' => "\xc2\xa4",  #currency sign
        '&yen;'    => "\xc2\xa5",  #yen sign = yuan sign
        '&brvbar;' => "\xc2\xa6",  #broken bar = broken vertical bar
        '&sect;'   => "\xc2\xa7",  #section sign
        '&uml;'    => "\xc2\xa8",  #diaeresis = spacing diaeresis
        '&copy;'   => "\xc2\xa9",  #copyright sign
        '&ordf;'   => "\xc2\xaa",  #feminine ordinal indicator
        '&laquo;'  => "\xc2\xab",  #left-pointing double angle quotation mark = left pointing guillemet («)
        '&not;'    => "\xc2\xac",  #not sign
        '&shy;'    => "\xc2\xad",  #soft hyphen = discretionary hyphen; 
                                   #non-breaking hyphen (неразрывный дефис): "\xe2\x80\x91" (U+2011)
        '&reg;'    => "\xc2\xae",  #registered sign = registered trade mark sign
        '&macr;'   => "\xc2\xaf",  #macron = spacing macron = overline = APL overbar
        '&deg;'    => "\xc2\xb0",  #degree sign
        '&plusmn;' => "\xc2\xb1",  #plus-minus sign = plus-or-minus sign
        '&sup2;'   => "\xc2\xb2",  #superscript two = superscript digit two = squared
        '&sup3;'   => "\xc2\xb3",  #superscript three = superscript digit three = cubed
        '&acute;'  => "\xc2\xb4",  #acute accent = spacing acute
        '&micro;'  => "\xc2\xb5",  #micro sign
        '&para;'   => "\xc2\xb6",  #pilcrow sign = paragraph sign
        '&middot;' => "\xc2\xb7",  #middle dot = Georgian comma = Greek middle dot
        '&cedil;'  => "\xc2\xb8",  #cedilla = spacing cedilla
        '&sup1;'   => "\xc2\xb9",  #superscript one = superscript digit one
        '&ordm;'   => "\xc2\xba",  #masculine ordinal indicator
        '&raquo;'  => "\xc2\xbb",  #right-pointing double angle quotation mark = right pointing guillemet (ї)
        '&frac14;' => "\xc2\xbc",  #vulgar fraction one quarter = fraction one quarter
        '&frac12;' => "\xc2\xbd",  #vulgar fraction one half = fraction one half
        '&frac34;' => "\xc2\xbe",  #vulgar fraction three quarters = fraction three quarters
        '&iquest;' => "\xc2\xbf",  #inverted question mark = turned question mark
      #Latin capital letter
        '&Agrave;' => "\xc3\x80",  #Latin capital letter A with grave = Latin capital letter A grave
        '&Aacute;' => "\xc3\x81",  #Latin capital letter A with acute
        '&Acirc;'  => "\xc3\x82",  #Latin capital letter A with circumflex
        '&Atilde;' => "\xc3\x83",  #Latin capital letter A with tilde
        '&Auml;'   => "\xc3\x84",  #Latin capital letter A with diaeresis
        '&Aring;'  => "\xc3\x85",  #Latin capital letter A with ring above = Latin capital letter A ring
        '&AElig;'  => "\xc3\x86",  #Latin capital letter AE = Latin capital ligature AE
        '&Ccedil;' => "\xc3\x87",  #Latin capital letter C with cedilla
        '&Egrave;' => "\xc3\x88",  #Latin capital letter E with grave
        '&Eacute;' => "\xc3\x89",  #Latin capital letter E with acute
        '&Ecirc;'  => "\xc3\x8a",  #Latin capital letter E with circumflex
        '&Euml;'   => "\xc3\x8b",  #Latin capital letter E with diaeresis
        '&Igrave;' => "\xc3\x8c",  #Latin capital letter I with grave
        '&Iacute;' => "\xc3\x8d",  #Latin capital letter I with acute
        '&Icirc;'  => "\xc3\x8e",  #Latin capital letter I with circumflex
        '&Iuml;'   => "\xc3\x8f",  #Latin capital letter I with diaeresis
        '&ETH;'    => "\xc3\x90",  #Latin capital letter ETH
        '&Ntilde;' => "\xc3\x91",  #Latin capital letter N with tilde
        '&Ograve;' => "\xc3\x92",  #Latin capital letter O with grave
        '&Oacute;' => "\xc3\x93",  #Latin capital letter O with acute
        '&Ocirc;'  => "\xc3\x94",  #Latin capital letter O with circumflex
        '&Otilde;' => "\xc3\x95",  #Latin capital letter O with tilde
        '&Ouml;'   => "\xc3\x96",  #Latin capital letter O with diaeresis
        '&times;'  => "\xc3\x97",  #multiplication sign
        '&Oslash;' => "\xc3\x98",  #Latin capital letter O with stroke = Latin capital letter O slash
        '&Ugrave;' => "\xc3\x99",  #Latin capital letter U with grave
        '&Uacute;' => "\xc3\x9a",  #Latin capital letter U with acute
        '&Ucirc;'  => "\xc3\x9b",  #Latin capital letter U with circumflex
        '&Uuml;'   => "\xc3\x9c",  #Latin capital letter U with diaeresis
        '&Yacute;' => "\xc3\x9d",  #Latin capital letter Y with acute
        '&THORN;'  => "\xc3\x9e",  #Latin capital letter THORN
      #Latin small letter
        '&szlig;'  => "\xc3\x9f",  #Latin small letter sharp s = ess-zed
        '&agrave;' => "\xc3\xa0",  #Latin small letter a with grave = Latin small letter a grave
        '&aacute;' => "\xc3\xa1",  #Latin small letter a with acute
        '&acirc;'  => "\xc3\xa2",  #Latin small letter a with circumflex
        '&atilde;' => "\xc3\xa3",  #Latin small letter a with tilde
        '&auml;'   => "\xc3\xa4",  #Latin small letter a with diaeresis
        '&aring;'  => "\xc3\xa5",  #Latin small letter a with ring above = Latin small letter a ring
        '&aelig;'  => "\xc3\xa6",  #Latin small letter ae = Latin small ligature ae
        '&ccedil;' => "\xc3\xa7",  #Latin small letter c with cedilla
        '&egrave;' => "\xc3\xa8",  #Latin small letter e with grave
        '&eacute;' => "\xc3\xa9",  #Latin small letter e with acute
        '&ecirc;'  => "\xc3\xaa",  #Latin small letter e with circumflex
        '&euml;'   => "\xc3\xab",  #Latin small letter e with diaeresis
        '&igrave;' => "\xc3\xac",  #Latin small letter i with grave
        '&iacute;' => "\xc3\xad",  #Latin small letter i with acute
        '&icirc;'  => "\xc3\xae",  #Latin small letter i with circumflex
        '&iuml;'   => "\xc3\xaf",  #Latin small letter i with diaeresis
        '&eth;'    => "\xc3\xb0",  #Latin small letter eth
        '&ntilde;' => "\xc3\xb1",  #Latin small letter n with tilde
        '&ograve;' => "\xc3\xb2",  #Latin small letter o with grave
        '&oacute;' => "\xc3\xb3",  #Latin small letter o with acute
        '&ocirc;'  => "\xc3\xb4",  #Latin small letter o with circumflex
        '&otilde;' => "\xc3\xb5",  #Latin small letter o with tilde
        '&ouml;'   => "\xc3\xb6",  #Latin small letter o with diaeresis
        '&divide;' => "\xc3\xb7",  #division sign
        '&oslash;' => "\xc3\xb8",  #Latin small letter o with stroke = Latin small letter o slash
        '&ugrave;' => "\xc3\xb9",  #Latin small letter u with grave
        '&uacute;' => "\xc3\xba",  #Latin small letter u with acute
        '&ucirc;'  => "\xc3\xbb",  #Latin small letter u with circumflex
        '&uuml;'   => "\xc3\xbc",  #Latin small letter u with diaeresis
        '&yacute;' => "\xc3\xbd",  #Latin small letter y with acute
        '&thorn;'  => "\xc3\xbe",  #Latin small letter thorn
        '&yuml;'   => "\xc3\xbf",  #Latin small letter y with diaeresis
      #Symbols and Greek Letters:
        '&fnof;'    => "\xc6\x92",  #Latin small f with hook = function = florin
        '&Alpha;'   => "\xce\x91",  #Greek capital letter alpha
        '&Beta;'    => "\xce\x92",  #Greek capital letter beta
        '&Gamma;'   => "\xce\x93",  #Greek capital letter gamma
        '&Delta;'   => "\xce\x94",  #Greek capital letter delta
        '&Epsilon;' => "\xce\x95",  #Greek capital letter epsilon
        '&Zeta;'    => "\xce\x96",  #Greek capital letter zeta
        '&Eta;'     => "\xce\x97",  #Greek capital letter eta
        '&Theta;'   => "\xce\x98",  #Greek capital letter theta
        '&Iota;'    => "\xce\x99",  #Greek capital letter iota
        '&Kappa;'   => "\xce\x9a",  #Greek capital letter kappa
        '&Lambda;'  => "\xce\x9b",  #Greek capital letter lambda
        '&Mu;'      => "\xce\x9c",  #Greek capital letter mu
        '&Nu;'      => "\xce\x9d",  #Greek capital letter nu
        '&Xi;'      => "\xce\x9e",  #Greek capital letter xi
        '&Omicron;' => "\xce\x9f",  #Greek capital letter omicron
        '&Pi;'      => "\xce\xa0",  #Greek capital letter pi
        '&Rho;'     => "\xce\xa1",  #Greek capital letter rho
        '&Sigma;'   => "\xce\xa3",  #Greek capital letter sigma
        '&Tau;'     => "\xce\xa4",  #Greek capital letter tau
        '&Upsilon;' => "\xce\xa5",  #Greek capital letter upsilon
        '&Phi;'     => "\xce\xa6",  #Greek capital letter phi
        '&Chi;'     => "\xce\xa7",  #Greek capital letter chi
        '&Psi;'     => "\xce\xa8",  #Greek capital letter psi
        '&Omega;'   => "\xce\xa9",  #Greek capital letter omega
        '&alpha;'   => "\xce\xb1",  #Greek small letter alpha
        '&beta;'    => "\xce\xb2",  #Greek small letter beta
        '&gamma;'   => "\xce\xb3",  #Greek small letter gamma
        '&delta;'   => "\xce\xb4",  #Greek small letter delta
        '&epsilon;' => "\xce\xb5",  #Greek small letter epsilon
        '&zeta;'    => "\xce\xb6",  #Greek small letter zeta
        '&eta;'     => "\xce\xb7",  #Greek small letter eta
        '&theta;'   => "\xce\xb8",  #Greek small letter theta
        '&iota;'    => "\xce\xb9",  #Greek small letter iota
        '&kappa;'   => "\xce\xba",  #Greek small letter kappa
        '&lambda;'  => "\xce\xbb",  #Greek small letter lambda
        '&mu;'      => "\xce\xbc",  #Greek small letter mu
        '&nu;'      => "\xce\xbd",  #Greek small letter nu
        '&xi;'      => "\xce\xbe",  #Greek small letter xi
        '&omicron;' => "\xce\xbf",  #Greek small letter omicron
        '&pi;'      => "\xcf\x80",  #Greek small letter pi
        '&rho;'     => "\xcf\x81",  #Greek small letter rho
        '&sigmaf;'  => "\xcf\x82",  #Greek small letter final sigma
        '&sigma;'   => "\xcf\x83",  #Greek small letter sigma
        '&tau;'     => "\xcf\x84",  #Greek small letter tau
        '&upsilon;' => "\xcf\x85",  #Greek small letter upsilon
        '&phi;'     => "\xcf\x86",  #Greek small letter phi
        '&chi;'     => "\xcf\x87",  #Greek small letter chi
        '&psi;'     => "\xcf\x88",  #Greek small letter psi
        '&omega;'   => "\xcf\x89",  #Greek small letter omega
        '&thetasym;'=> "\xcf\x91",  #Greek small letter theta symbol
        '&upsih;'   => "\xcf\x92",  #Greek upsilon with hook symbol
        '&piv;'     => "\xcf\x96",  #Greek pi symbol

        '&bull;'    => "\xe2\x80\xa2",  #bullet = black small circle
        '&hellip;'  => "\xe2\x80\xa6",  #horizontal ellipsis = three dot leader
        '&prime;'   => "\xe2\x80\xb2",  #prime = minutes = feet (для обозначения минут и футов)
        '&Prime;'   => "\xe2\x80\xb3",  #double prime = seconds = inches (для обозначения секунд и діймов).
        '&oline;'   => "\xe2\x80\xbe",  #overline = spacing overscore
        '&frasl;'   => "\xe2\x81\x84",  #fraction slash
        '&weierp;'  => "\xe2\x84\x98",  #script capital P = power set = Weierstrass p
        '&image;'   => "\xe2\x84\x91",  #blackletter capital I = imaginary part
        '&real;'    => "\xe2\x84\x9c",  #blackletter capital R = real part symbol
        '&trade;'   => "\xe2\x84\xa2",  #trade mark sign
        '&alefsym;' => "\xe2\x84\xb5",  #alef symbol = first transfinite cardinal
        '&larr;'    => "\xe2\x86\x90",  #leftwards arrow
        '&uarr;'    => "\xe2\x86\x91",  #upwards arrow
        '&rarr;'    => "\xe2\x86\x92",  #rightwards arrow
        '&darr;'    => "\xe2\x86\x93",  #downwards arrow
        '&harr;'    => "\xe2\x86\x94",  #left right arrow
        '&crarr;'   => "\xe2\x86\xb5",  #downwards arrow with corner leftwards = carriage return
        '&lArr;'    => "\xe2\x87\x90",  #leftwards double arrow
        '&uArr;'    => "\xe2\x87\x91",  #upwards double arrow
        '&rArr;'    => "\xe2\x87\x92",  #rightwards double arrow
        '&dArr;'    => "\xe2\x87\x93",  #downwards double arrow
        '&hArr;'    => "\xe2\x87\x94",  #left right double arrow
        '&forall;'  => "\xe2\x88\x80",  #for all
        '&part;'    => "\xe2\x88\x82",  #partial differential
        '&exist;'   => "\xe2\x88\x83",  #there exists
        '&empty;'   => "\xe2\x88\x85",  #empty set = null set = diameter
        '&nabla;'   => "\xe2\x88\x87",  #nabla = backward difference
        '&isin;'    => "\xe2\x88\x88",  #element of
        '&notin;'   => "\xe2\x88\x89",  #not an element of
        '&ni;'      => "\xe2\x88\x8b",  #contains as member
        '&prod;'    => "\xe2\x88\x8f",  #n-ary product = product sign
        '&sum;'     => "\xe2\x88\x91",  #n-ary sumation
        '&minus;'   => "\xe2\x88\x92",  #minus sign
        '&lowast;'  => "\xe2\x88\x97",  #asterisk operator
        '&radic;'   => "\xe2\x88\x9a",  #square root = radical sign
        '&prop;'    => "\xe2\x88\x9d",  #proportional to
        '&infin;'   => "\xe2\x88\x9e",  #infinity
        '&ang;'     => "\xe2\x88\xa0",  #angle
        '&and;'     => "\xe2\x88\xa7",  #logical and = wedge
        '&or;'      => "\xe2\x88\xa8",  #logical or = vee
        '&cap;'     => "\xe2\x88\xa9",  #intersection = cap
        '&cup;'     => "\xe2\x88\xaa",  #union = cup
        '&int;'     => "\xe2\x88\xab",  #integral
        '&there4;'  => "\xe2\x88\xb4",  #therefore
        '&sim;'     => "\xe2\x88\xbc",  #tilde operator = varies with = similar to
        '&cong;'    => "\xe2\x89\x85",  #approximately equal to
        '&asymp;'   => "\xe2\x89\x88",  #almost equal to = asymptotic to
        '&ne;'      => "\xe2\x89\xa0",  #not equal to
        '&equiv;'   => "\xe2\x89\xa1",  #identical to
        '&le;'      => "\xe2\x89\xa4",  #less-than or equal to
        '&ge;'      => "\xe2\x89\xa5",  #greater-than or equal to
        '&sub;'     => "\xe2\x8a\x82",  #subset of
        '&sup;'     => "\xe2\x8a\x83",  #superset of
        '&nsub;'    => "\xe2\x8a\x84",  #not a subset of
        '&sube;'    => "\xe2\x8a\x86",  #subset of or equal to
        '&supe;'    => "\xe2\x8a\x87",  #superset of or equal to
        '&oplus;'   => "\xe2\x8a\x95",  #circled plus = direct sum
        '&otimes;'  => "\xe2\x8a\x97",  #circled times = vector product
        '&perp;'    => "\xe2\x8a\xa5",  #up tack = orthogonal to = perpendicular
        '&sdot;'    => "\xe2\x8b\x85",  #dot operator
        '&lceil;'   => "\xe2\x8c\x88",  #left ceiling = APL upstile
        '&rceil;'   => "\xe2\x8c\x89",  #right ceiling
        '&lfloor;'  => "\xe2\x8c\x8a",  #left floor = APL downstile
        '&rfloor;'  => "\xe2\x8c\x8b",  #right floor
        '&lang;'    => "\xe2\x8c\xa9",  #left-pointing angle bracket = bra
        '&rang;'    => "\xe2\x8c\xaa",  #right-pointing angle bracket = ket
        '&loz;'     => "\xe2\x97\x8a",  #lozenge
        '&spades;'  => "\xe2\x99\xa0",  #black spade suit
        '&clubs;'   => "\xe2\x99\xa3",  #black club suit = shamrock
        '&hearts;'  => "\xe2\x99\xa5",  #black heart suit = valentine
        '&diams;'   => "\xe2\x99\xa6",  #black diamond suit
      #Other Special Characters:
        '&OElig;'  => "\xc5\x92",  #Latin capital ligature OE
        '&oelig;'  => "\xc5\x93",  #Latin small ligature oe
        '&Scaron;' => "\xc5\xa0",  #Latin capital letter S with caron
        '&scaron;' => "\xc5\xa1",  #Latin small letter s with caron
        '&Yuml;'   => "\xc5\xb8",  #Latin capital letter Y with diaeresis
        '&circ;'   => "\xcb\x86",  #modifier letter circumflex accent
        '&tilde;'  => "\xcb\x9c",  #small tilde
        '&ensp;'   => "\xe2\x80\x82",  #en space
        '&emsp;'   => "\xe2\x80\x83",  #em space
        '&thinsp;' => "\xe2\x80\x89",  #thin space
        '&zwnj;'   => "\xe2\x80\x8c",  #zero width non-joiner
        '&zwj;'    => "\xe2\x80\x8d",  #zero width joiner
        '&lrm;'    => "\xe2\x80\x8e",  #left-to-right mark
        '&rlm;'    => "\xe2\x80\x8f",  #right-to-left mark
        '&ndash;'  => "\xe2\x80\x93",  #en dash
        '&mdash;'  => "\xe2\x80\x94",  #em dash
        '&lsquo;'  => "\xe2\x80\x98",  #left single quotation mark
        '&rsquo;'  => "\xe2\x80\x99",  #right single quotation mark (and apostrophe!)
        '&sbquo;'  => "\xe2\x80\x9a",  #single low-9 quotation mark
        '&ldquo;'  => "\xe2\x80\x9c",  #left double quotation mark
        '&rdquo;'  => "\xe2\x80\x9d",  #right double quotation mark
        '&bdquo;'  => "\xe2\x80\x9e",  #double low-9 quotation mark
        '&dagger;' => "\xe2\x80\xa0",  #dagger
        '&Dagger;' => "\xe2\x80\xa1",  #double dagger
        '&permil;' => "\xe2\x80\xb0",  #per mille sign
        '&lsaquo;' => "\xe2\x80\xb9",  #single left-pointing angle quotation mark
        '&rsaquo;' => "\xe2\x80\xba",  #single right-pointing angle quotation mark
        '&euro;'   => "\xe2\x82\xac",  #euro sign
    ));

    #заменяем utf8-символы на именованные сущности:
    #оптимизация скорости: заменяем только те символы, которые используются в html коде!
    preg_match_all('/  [\xc2\xc3\xc5\xc6\xcb\xce\xcf][\x80-\xbf]  #2 bytes
                     | \xe2[\x80-\x99][\x82-\xac]                 #3 bytes
                     /sx', $s, $m);
    foreach (array_unique($m[0]) as $char)
    {
        if (array_key_exists($char, $table)) $s = str_replace($char, $table[$char], $s);
    }#foreach

    return $s;
}